Load and Explore Data
strokes <- read.csv("./data/stroke_data.csv")
str(strokes)
'data.frame': 5110 obs. of 12 variables:
$ id : int 9046 51676 31112 60182 1665 56669 53882 10434 27419 60491 ...
$ gender : chr "Male" "Female" "Male" "Female" ...
$ age : num 67 61 80 49 79 81 74 69 59 78 ...
$ hypertension : int 0 0 0 0 1 0 1 0 0 0 ...
$ heart_disease : int 1 0 1 0 0 0 1 0 0 0 ...
$ ever_married : chr "Yes" "Yes" "Yes" "Yes" ...
$ work_type : chr "Private" "Self-employed" "Private" "Private" ...
$ Residence_type : chr "Urban" "Rural" "Rural" "Urban" ...
$ avg_glucose_level: num 229 202 106 171 174 ...
$ bmi : chr "36.6" "N/A" "32.5" "34.4" ...
$ smoking_status : chr "formerly smoked" "never smoked" "never smoked" "smokes" ...
$ stroke : int 1 1 1 1 1 1 1 1 1 1 ...
summary(strokes)
id gender age hypertension heart_disease
Min. : 67 Length:5110 Min. : 0.08 Min. :0.00000 Min. :0.00000
1st Qu.:17741 Class :character 1st Qu.:25.00 1st Qu.:0.00000 1st Qu.:0.00000
Median :36932 Mode :character Median :45.00 Median :0.00000 Median :0.00000
Mean :36518 Mean :43.23 Mean :0.09746 Mean :0.05401
3rd Qu.:54682 3rd Qu.:61.00 3rd Qu.:0.00000 3rd Qu.:0.00000
Max. :72940 Max. :82.00 Max. :1.00000 Max. :1.00000
ever_married work_type Residence_type avg_glucose_level bmi
Length:5110 Length:5110 Length:5110 Min. : 55.12 Length:5110
Class :character Class :character Class :character 1st Qu.: 77.25 Class :character
Mode :character Mode :character Mode :character Median : 91.89 Mode :character
Mean :106.15
3rd Qu.:114.09
Max. :271.74
smoking_status stroke
Length:5110 Min. :0.00000
Class :character 1st Qu.:0.00000
Mode :character Median :0.00000
Mean :0.04873
3rd Qu.:0.00000
Max. :1.00000
Verify that the ID column is in fact unique
Remove ID column since this is an assigned identifier and as such does not impact stroke
uniqueID <- unique(strokes$id)
length(uniqueID)
[1] 5110
dim(strokes)
[1] 5110 12
strokes <- strokes[-1]
Since all the positive stroke values are found at the start of the file we will randomize the dataframe
set.seed(42)
rows <- sample(nrow(strokes))
strokes <- strokes[rows,]
Review category distrubution for categorical variables
Update binary categorical variables to numeric
remove gender with value of ‘other’, code "Female as 0, male as 1
strokes <- strokes %>% filter(gender != "Other")
strokes$gender <- ifelse(strokes$gender == "Female", 0,1)
convert residence type to binary response. 0=rural, 1=urban
strokes$Residence_type = ifelse(strokes$Residence_type == "Rural", 0, 1)
convert ever married to binary response. 0=No, 1=Yes
strokes$ever_married <- ifelse(strokes$ever_married == 'No', 0,1)
Convert stroke to ‘yes/no’ values
strokes$stroke <- ifelse(strokes$stroke == 1, "Yes", "No")
Convert multi-category categorical features to factors
# strokes$gender <- as.factor(strokes$gender)
# strokes$hypertension <- as.factor(strokes$hypertension)
# strokes$heart_disease <- as.factor(strokes$heart_disease)
# strokes$ever_married <- as.factor(strokes$ever_married)
strokes$work_type <- as.factor(strokes$work_type)
# strokes$Residence_type <- as.factor(strokes$Residence_type)
strokes$smoking_status <- as.factor(strokes$smoking_status)
strokes$stroke <- as.factor(strokes$stroke)
strokes$smoking_status <- revalue(strokes$smoking_status, c("formerly smoked"="formerly_smoked", "never smoked"="never_smoked", "smokes"="smokes", "Unknown"="unknown"))
strokes$work_type <- revalue(strokes$work_type, c("Self-employed"="Self_employed"))
Clean bmi feature
- Convert “N/A” entries to NA values
- Convert from character to numeric values
strokes <- na_if(strokes,"N/A")
strokes$bmi <- as.numeric(strokes$bmi)
dim(strokes)
[1] 5109 11
Data Exploration
Missing Values
Table continues below
| 0 |
0 |
0 |
0 |
0 |
0 |
require(tidyverse)
require(rcompanion)
# Calculate a pairwise association between all variables in a data-frame. In particular nominal vs nominal with Chi-square, numeric vs numeric with Pearson correlation, and nominal vs numeric with ANOVA.
# Adopted from https://stackoverflow.com/a/52557631/590437
mixed_assoc = function(df, cor_method="spearman", adjust_cramersv_bias=TRUE){
df_comb = expand.grid(names(df), names(df), stringsAsFactors = F) %>% set_names("X1", "X2")
is_nominal = function(x) class(x) %in% c("factor", "character")
# https://community.rstudio.com/t/why-is-purr-is-numeric-deprecated/3559
# https://github.com/r-lib/rlang/issues/781
is_numeric <- function(x) { is.integer(x) || is_double(x)}
f = function(xName,yName) {
x = pull(df, xName)
y = pull(df, yName)
result = if(is_nominal(x) && is_nominal(y)){
# use bias corrected cramersV as described in https://rdrr.io/cran/rcompanion/man/cramerV.html
cv = cramerV(as.character(x), as.character(y), bias.correct = adjust_cramersv_bias)
data.frame(xName, yName, assoc=cv, type="cramersV")
}else if(is_numeric(x) && is_numeric(y)){
correlation = cor(x, y, method=cor_method, use="complete.obs")
data.frame(xName, yName, assoc=correlation, type="correlation")
}else if(is_numeric(x) && is_nominal(y)){
# from https://stats.stackexchange.com/questions/119835/correlation-between-a-nominal-iv-and-a-continuous-dv-variable/124618#124618
r_squared = summary(lm(x ~ y))$r.squared
data.frame(xName, yName, assoc=sqrt(r_squared), type="anova")
}else if(is_nominal(x) && is_numeric(y)){
r_squared = summary(lm(y ~x))$r.squared
data.frame(xName, yName, assoc=sqrt(r_squared), type="anova")
}else {
warning(paste("unmatched column type combination: ", class(x), class(y)))
}
# finally add complete obs number and ratio to table
result %>% mutate(complete_obs_pairs=sum(!is.na(x) & !is.na(y)), complete_obs_ratio=complete_obs_pairs/length(x)) %>% rename(x=xName, y=yName)
}
# apply function to each variable combination
map2_df(df_comb$X1, df_comb$X2, f)
}
Associations between features
associations <- mixed_assoc(strokes)
associations
bmi imputation
bmi has multiple missing values. Instead of using the mean or median of the entire data set to impute the missing values determine if a second feature has an association with bmi
association of bmi with other variables
bmi_assoc <- associations %>% filter(y == "bmi")
Plots of bmi vs other features
# ggplot(data=associations, aes(x=x, y=y, fill=assoc)) + geom_tile() + theme(axis.text.x=element_text(angle=90, vjust=0.5, hjust=1), axis.title = element_blank(), plot.title=element_text(hjust=0.5)) + ggtitle("Association Between Features")
ggplot(data=bmi_assoc, aes(x=x, y=y, fill=assoc)) + geom_tile() + theme(axis.text.x=element_text(angle=90, vjust=0.5, hjust=1, size=20), axis.text.y = element_text(size=20),axis.title = element_blank(), plot.title=element_text(hjust=0.5)) + ggtitle("Association with bmi")

plot(bmi ~ ., data=strokes)










Age has an association with bmi, so the data will be binned by age and the missing bmi values will be imputed based upon the median value of the bin they fall in
Verify missing values are resolved and data types are as expected
Missing Values
Table continues below
| 0 |
0 |
0 |
0 |
0 |
0 |
str(strokes)
tibble [5,109 × 11] (S3: tbl_df/tbl/data.frame)
$ gender : num [1:5109] 0 0 0 1 1 0 1 1 0 1 ...
$ age : num [1:5109] 56 16 28 52 4 42 65 31 66 64 ...
$ hypertension : int [1:5109] 0 0 0 0 0 0 0 0 1 0 ...
$ heart_disease : int [1:5109] 0 0 0 0 0 0 0 0 0 0 ...
$ ever_married : num [1:5109] 1 0 0 1 0 1 1 1 1 1 ...
$ work_type : Factor w/ 5 levels "children","Govt_job",..: 4 3 4 4 1 2 4 2 4 4 ...
$ Residence_type : num [1:5109] 0 1 0 1 0 1 1 1 0 1 ...
$ avg_glucose_level: num [1:5109] 77.7 102.1 96.9 229.2 103.8 ...
$ bmi : num [1:5109] 40.8 27.1 29 35.6 15.9 29.8 28.4 30.4 39.5 28.3 ...
$ smoking_status : Factor w/ 4 levels "formerly_smoked",..: 2 2 4 1 4 4 3 1 2 4 ...
$ stroke : Factor w/ 2 levels "No","Yes": 1 1 1 1 1 1 1 1 1 1 ...
Explore relation of features with response variable ‘stroke’
p1 <- ggplot(data=strokes, aes(stroke, fill=as.factor(gender))) + geom_bar(position='fill') + labs(fill="gender")
p2 <- ggplot(data=strokes, aes(stroke, age)) + geom_boxplot()
p3 <- ggplot(data=strokes, aes(stroke, fill=as.factor(hypertension))) + geom_bar(position='fill') + labs(fill="hypertension")
p4 <- ggplot(data=strokes, aes(stroke, fill=as.factor(heart_disease))) + geom_bar(position='fill') + labs(fill='heart_disease')
p5 <- ggplot(data=strokes, aes(stroke, fill=as.factor(ever_married))) + geom_bar(position='fill') + labs(fill="ever_married")
p6 <- ggplot(data=strokes, aes(stroke, fill=work_type)) + geom_bar(position='fill')
p7 <- ggplot(data=strokes, aes(stroke, fill=as.factor(Residence_type))) + geom_bar(position='fill') + labs(fill="Residence_type")
p8 <- ggplot(data=strokes, aes(stroke, avg_glucose_level)) + geom_boxplot()
p9 <- ggplot(data=strokes, aes(stroke, bmi)) + geom_boxplot()
p10 <- ggplot(data=strokes, aes(stroke, fill=smoking_status)) + geom_bar(position='fill')
grid.arrange(p1,p2,p3,p4,nrow=2)

grid.arrange(p5,p6,p7,p8,nrow=2)

grid.arrange(p9,p10,nrow=1)

Chi-squared approximation may be incorrect
Chi-Square Tests
| smoking_status |
2.008e-06 |
| gender |
0.5598 |
| hypertension |
1.689e-19 |
| heart_disease |
2.121e-21 |
| ever_married |
1.686e-14 |
| work_type |
5.409e-10 |
| residence_type |
0.2998 |
T-Tests
| age |
2.176e-95 |
| glucose |
2.373e-11 |
| bmi |
0.0003064 |
The features gender and residence type do not show a statistically significant association with the stroke outcome. This can be observed visually as well as by the Chi-Square tests.
Review distribution of numeric features
hist(strokes$bmi, main="bmi", xlab="bmi")

hist(strokes$avg_glucose_level, main="average glucose level", xlab="avg_glucose_level")

hist(strokes$age, main="age", xlab="age")

NA
NA
Both avg_glucose_level and bmi are right-skewed. To help correct this both variables will be replaced with their respective log values
strokes$bmi <- log(strokes$bmi)
strokes$avg_glucose_level <- log(strokes$avg_glucose_level)
hist(strokes$bmi)

hist(strokes$avg_glucose_level)

Since neither gender nor residence_type show any statistically significant correlation with the stroke outcome both features will be removed to avoid any unintentional modle complexity or noise
strokes <- strokes[-c(1,7)]
Prepare Data Sets for Model Usage
- Split into train, validation and test datasets
- Create balanced training data sets
- Separate into features and labels
- Scale Datasets
- One-Hot Encode Datasets
# create train/test dataset as well as subset of train for cross-validation and evaluation usage
set.seed(123)
selection <- createDataPartition(strokes$stroke, p=0.8, list=FALSE)
trainDataFull = strokes[selection,]
testData = strokes[-selection,]
set.seed(123)
selection2 <- createDataPartition(trainDataFull$stroke, p=0.8, list=FALSE)
trainData = trainDataFull[selection2,]
valData = trainDataFull[-selection2,]
#create balanced training datasets
trainDataBalanced <- ROSE(stroke ~ ., data=trainData, p=0.5)$data
trainDataFullBalanced <- ROSE(stroke ~ ., data=trainDataFull, p=0.5)$data
# separate features and labels
valDataFeatures <- valData[-9]
valDataLabels <- valData[9]
testDataFeatures <- testData[-9]
testDataLabels <- testData[9]
trainDataFeatures <- trainData[-9]
trainDataLabels <- trainData[9]
trainDataFullFeatures <- trainDataFull[-9]
trainDataFullLabels <- trainDataFull[9]
trainDataBalancedFeatures <- trainDataBalanced[-9]
trainDataBalancedLabels <- trainDataBalanced[9]
trainDataFullBalancedFeatures <- trainDataFullBalanced[-9]
trainDataFullBalancedLabels <- trainDataFullBalanced[9]
# scale features data sets. testData scales with trainDataFull while valData scales with trainData
preProcScaleFullTrain <- preProcess(trainDataFullBalancedFeatures, method=c("center", "scale"))
preProcScaleTrain <- preProcess(trainDataBalancedFeatures, method=c("center", "scale"))
trainDataFullBalancedFeatures_s <- predict(preProcScaleFullTrain, trainDataFullBalancedFeatures)
testDataFeatures_s <- predict(preProcScaleFullTrain, testDataFeatures)
trainDataBalancedFeatures_s <- predict(preProcScaleTrain, trainDataBalancedFeatures)
valDataFeatures_s <- predict(preProcScaleTrain, valDataFeatures)
# one-hot encode datasets
dummy1 <- dummyVars("~ .", data=trainDataFullBalancedFeatures_s)
trainDataFullBalancedFeatures_se <- data.frame(predict(dummy1,newdata=trainDataFullBalancedFeatures_s))
dummy2 <- dummyVars("~ .", data=trainDataBalancedFeatures)
trainDataBalancedFeatures_e <- data.frame(predict(dummy2,newdata=trainDataBalancedFeatures))
dummy3 <- dummyVars("~ .", data=trainDataFeatures)
trainDataFeatures_e <- data.frame(predict(dummy3,newdata=trainDataFeatures))
dummy4 <- dummyVars("~ .", data=trainDataFullFeatures)
trainDataFullFeatures_e <- data.frame(predict(dummy4,newdata=trainDataFullFeatures))
testDataFeatures_se <- data.frame(predict(dummy1,newdata=testDataFeatures_s))
valDataFeatures_e <- data.frame(predict(dummy2,newdata=valDataFeatures))
fullsize <- dim(trainDataFull)
trainsize <- dim(trainData)
valsize <- dim(valData)
testsize <- dim(testData)
distfull <- table(trainDataFull$stroke)
disttrain <- table(trainData$stroke)
distval <- table(valData$stroke)
disttest <- table(testData$stroke)
Verify dimensions and distribution of response variable for datasets
The proportion of “no” to “yes” values in all datasets is equivalent.
Fit Models
Define control for models trained with Caret
myControl <- trainControl(method='repeatedcv',
number=10,
repeats=5,
verboseIter = FALSE,
classProbs=TRUE,
summaryFunction=twoClassSummary,
selectionFunction="oneSE",
sampling='rose')
myControl2 <- trainControl(method='repeatedcv',
number=10,
repeats=5,
verboseIter = FALSE,
classProbs=TRUE,
summaryFunction=twoClassSummary,
selectionFunction="oneSE")
KNN Model Training and Evaluation
Ensuring the square root of the number of observations is contained in the values of k being explored
set.seed(123)
knn_grid <- expand.grid(k=seq(1,100,by=2))
knn_train <- cbind(trainDataFeatures_e, trainDataLabels)
knn_model <- caret::train(stroke ~ ., data=knn_train, method='knn', trControl=myControl, tuneGrid = knn_grid, preProc = 'range', metric='ROC')
plot(knn_model)

knn_pred <- predict(knn_model, newdata=valDataFeatures_e)
knn_prob <- predict(knn_model, newdata=valDataFeatures_e, type='prob')
knn_results <- confusionMatrix(knn_pred, valDataLabels$stroke, positive = "Yes")
knn_results2 <- data.frame(as.factor(knn_pred), as.factor(valDataLabels$stroke), knn_prob)
colnames(knn_results2) <- c("pred", "obs", "No", "Yes")
knn_model
k-Nearest Neighbors
3271 samples
15 predictor
2 classes: 'No', 'Yes'
Pre-processing: re-scaling to [0, 1] (15)
Resampling: Cross-Validated (10 fold, repeated 5 times)
Summary of sample sizes: 2943, 2944, 2944, 2944, 2944, 2944, ...
Addtional sampling using ROSE prior to pre-processing
Resampling results across tuning parameters:
k ROC Sens Spec
1 0.6105385 0.8048271 0.41625
3 0.6951932 0.8315020 0.42375
5 0.7247873 0.8163251 0.46125
7 0.7548163 0.8279003 0.45875
9 0.7566825 0.8289298 0.47500
11 0.7614193 0.8243650 0.49000
13 0.7684376 0.8212126 0.49750
15 0.7703217 0.8217953 0.50625
17 0.7678556 0.8244956 0.47125
19 0.7788091 0.8132480 0.51250
21 0.7773042 0.8133047 0.50125
23 0.7771587 0.8135640 0.53500
25 0.7789210 0.8165871 0.51375
27 0.7874020 0.8105468 0.54000
29 0.7852502 0.8106664 0.52125
31 0.7866838 0.8080365 0.55000
33 0.7889458 0.8060413 0.54125
35 0.7820573 0.8046302 0.55500
37 0.7818359 0.8024411 0.54125
39 0.7847358 0.8050791 0.54125
41 0.7870788 0.7989082 0.58000
43 0.7808888 0.7982614 0.56250
45 0.7808209 0.7917104 0.56625
47 0.7874359 0.7952428 0.57250
49 0.7784092 0.7935706 0.56750
51 0.7859306 0.7906159 0.58875
53 0.7840340 0.7929279 0.58125
55 0.7859753 0.7889426 0.59125
57 0.7896676 0.7908739 0.59125
59 0.7863470 0.7875934 0.59125
61 0.7871621 0.7915774 0.58625
63 0.7889519 0.7896496 0.58625
65 0.7865711 0.7832791 0.60250
67 0.7867171 0.7789822 0.60750
69 0.7893115 0.7787804 0.61375
71 0.7886373 0.7805204 0.61500
73 0.7825669 0.7715238 0.61000
75 0.7852127 0.7722908 0.61750
77 0.7892454 0.7787225 0.61125
79 0.7843856 0.7742163 0.60875
81 0.7882281 0.7762794 0.61625
83 0.7847296 0.7742798 0.61125
85 0.7900390 0.7643229 0.63500
87 0.7892112 0.7690075 0.63750
89 0.7883087 0.7713262 0.60875
91 0.7825409 0.7645117 0.61250
93 0.7864030 0.7708818 0.61625
95 0.7885036 0.7717120 0.61500
97 0.7855760 0.7701090 0.61875
99 0.7874074 0.7652774 0.64750
ROC was used to select the optimal model using the one SE rule.
The final value used for the model was k = 99.
knn_results
Confusion Matrix and Statistics
Reference
Prediction No Yes
No 604 15
Yes 173 25
Accuracy : 0.7699
95% CI : (0.7395, 0.7983)
No Information Rate : 0.951
P-Value [Acc > NIR] : 1
Kappa : 0.14
Mcnemar's Test P-Value : <2e-16
Sensitivity : 0.62500
Specificity : 0.77735
Pos Pred Value : 0.12626
Neg Pred Value : 0.97577
Prevalence : 0.04896
Detection Rate : 0.03060
Detection Prevalence : 0.24235
Balanced Accuracy : 0.70117
'Positive' Class : Yes
knn_2class <- twoClassSummary(knn_results2, lev = c("No", "Yes"))
knn_2class[1]
ROC
0.7888996
knn_roc <- roc(valDataLabels$stroke ~ knn_prob$Yes, plot=TRUE, print.auc=TRUE, col='black', lwd=4, legacy.axes=TRUE, main="ROC Curve", add=FALSE)
Setting levels: control = No, case = Yes
Setting direction: controls < cases

Logistic Regression Model with Elastic Net Regularization
set.seed(123)
glm_grid <- expand.grid(alpha=seq(0,1, length=10), lambda = 10^seq(-3,3, length=100))
glm_train <- cbind(trainDataFeatures_e, trainDataLabels)
glm_model <- caret::train(stroke ~ ., data=glm_train, method='glmnet', trControl=myControl, tuneGrid = glm_grid, preProc = "range", metric='ROC', family='binomial')
glm_pred <- predict(glm_model, newdata = valDataFeatures_e)
glm_prob <- predict(glm_model, newdata = valDataFeatures_e, type='prob')
glm_results <- confusionMatrix(glm_pred, valDataLabels$stroke, positive="Yes")
glm_results
Confusion Matrix and Statistics
Reference
Prediction No Yes
No 551 6
Yes 226 34
Accuracy : 0.716
95% CI : (0.6838, 0.7467)
No Information Rate : 0.951
P-Value [Acc > NIR] : 1
Kappa : 0.155
Mcnemar's Test P-Value : <2e-16
Sensitivity : 0.85000
Specificity : 0.70914
Pos Pred Value : 0.13077
Neg Pred Value : 0.98923
Prevalence : 0.04896
Detection Rate : 0.04162
Detection Prevalence : 0.31824
Balanced Accuracy : 0.77957
'Positive' Class : Yes
glm_results2 <- data.frame(as.factor(glm_pred), as.factor(valDataLabels$stroke), glm_prob)
colnames(glm_results2) <- c("pred", "obs", "No", "Yes")
glm_2class <- twoClassSummary(glm_results2, lev=c("No", "Yes"))
glm_2class[1]
ROC
0.8555341
glm_roc <- roc(valDataLabels$stroke ~ glm_prob$Yes, plot=TRUE, print.auc=TRUE, col='black', lwd=4, legacy.axes=TRUE, main="ROC Curve", add=FALSE)
Setting levels: control = No, case = Yes
Setting direction: controls < cases

coef(glm_model$finalModel,glm_model$bestTune$lambda)
16 x 1 sparse Matrix of class "dgCMatrix"
1
(Intercept) -1.166219516
age 1.480281231
hypertension 0.333618442
heart_disease 0.001788369
ever_married 0.211458168
work_type.children -0.249526776
work_type.Govt_job .
work_type.Never_worked .
work_type.Private .
work_type.Self_employed .
avg_glucose_level 0.124124895
bmi .
smoking_status.formerly_smoked .
smoking_status.never_smoked .
smoking_status.smokes .
smoking_status.unknown .
varImp(glm_model)
glmnet variable importance
Decision Tree Model
set.seed(123)
tree_grid <- expand.grid(cp=seq(0.0,1.0,by=.001))
tree_data <- cbind(trainDataFeatures, trainDataLabels)
# tree_model <- caret::train(stroke ~.,data=tree_data, method='rpart', trControl=myControl, preProc = "range", metric='ROC', tuneGrid=tree_grid)
tree_model <- caret::train(stroke ~.,data=tree_data, method='rpart', trControl=myControl, metric='ROC', tuneGrid=tree_grid)
tree_pred <- predict(tree_model, valDataFeatures)
tree_prob <- predict(tree_model, newdata = valDataFeatures, type='prob')
tree_results <- confusionMatrix(tree_pred, valDataLabels$stroke, positive="Yes")
tree_results
Confusion Matrix and Statistics
Reference
Prediction No Yes
No 0 0
Yes 777 40
Accuracy : 0.049
95% CI : (0.0352, 0.0661)
No Information Rate : 0.951
P-Value [Acc > NIR] : 1
Kappa : 0
Mcnemar's Test P-Value : <2e-16
Sensitivity : 1.00000
Specificity : 0.00000
Pos Pred Value : 0.04896
Neg Pred Value : NaN
Prevalence : 0.04896
Detection Rate : 0.04896
Detection Prevalence : 1.00000
Balanced Accuracy : 0.50000
'Positive' Class : Yes
tree_results2 <- data.frame(as.factor(tree_pred), as.factor(valDataLabels$stroke), tree_prob)
colnames(tree_results2) <- c("pred", "obs", "No", "Yes")
tree_2class <- twoClassSummary(tree_results2, lev=c("No", "Yes"))
tree_2class[1]
ROC
0.5
tree_roc <- roc(valDataLabels$stroke ~ tree_prob$Yes, plot=TRUE, print.auc=TRUE, col='black', lwd=4, legacy.axes=TRUE, main="ROC Curve", add=FALSE)
Setting levels: control = No, case = Yes
Setting direction: controls < cases

fancyRpartPlot(tree_model$finalModel)

Considering the poor performance of the decision tree, a second model will be built but will not balance the dataset prior to use
set.seed(123)
tree_grid <- expand.grid(cp=seq(0.0,1.0,by=.001))
tree_data <- cbind(trainDataFeatures, trainDataLabels)
tree_model <- caret::train(stroke ~.,data=tree_data, method='rpart', trControl=myControl2, preProc = "range", metric='ROC', tuneGrid=tree_grid)
tree_pred <- predict(tree_model, valDataFeatures)
tree_prob <- predict(tree_model, newdata = valDataFeatures, type='prob')
tree_results <- confusionMatrix(tree_pred, valDataLabels$stroke, positive="Yes")
tree_results
Confusion Matrix and Statistics
Reference
Prediction No Yes
No 772 36
Yes 5 4
Accuracy : 0.9498
95% CI : (0.9325, 0.9638)
No Information Rate : 0.951
P-Value [Acc > NIR] : 0.6049
Kappa : 0.1479
Mcnemar's Test P-Value : 2.797e-06
Sensitivity : 0.100000
Specificity : 0.993565
Pos Pred Value : 0.444444
Neg Pred Value : 0.955446
Prevalence : 0.048960
Detection Rate : 0.004896
Detection Prevalence : 0.011016
Balanced Accuracy : 0.546782
'Positive' Class : Yes
tree_results2 <- data.frame(as.factor(tree_pred), as.factor(valDataLabels$stroke), tree_prob)
colnames(tree_results2) <- c("pred", "obs", "No", "Yes")
tree_2class <- twoClassSummary(tree_results2, lev=c("No", "Yes"))
tree_2class[1]
ROC
0.706435
tree_roc <- roc(valDataLabels$stroke ~ tree_prob$Yes, plot=TRUE, print.auc=TRUE, col='black', lwd=4, legacy.axes=TRUE, main="ROC Curve", add=FALSE)
Setting levels: control = No, case = Yes
Setting direction: controls < cases

fancyRpartPlot(tree_model$finalModel)

Random Forest Model
rf_grid <- expand.grid(mtry=c(1,2,4,6,8))
set.seed(123)
rf_data <- cbind(trainDataFeatures, trainDataLabels)
rf_model <- caret::train(stroke ~.,data=rf_data, method='rf', trControl=myControl, preProc = "range", metric='ROC', tuneGrid=rf_grid)
rf_pred <- predict(rf_model, valDataFeatures)
rf_prob <- predict(rf_model, valDataFeatures, type='prob')
rf_results <- confusionMatrix(rf_pred,valDataLabels$stroke, positive="Yes")
rf_results
Confusion Matrix and Statistics
Reference
Prediction No Yes
No 416 2
Yes 361 38
Accuracy : 0.5557
95% CI : (0.5209, 0.5901)
No Information Rate : 0.951
P-Value [Acc > NIR] : 1
Kappa : 0.0923
Mcnemar's Test P-Value : <2e-16
Sensitivity : 0.95000
Specificity : 0.53539
Pos Pred Value : 0.09524
Neg Pred Value : 0.99522
Prevalence : 0.04896
Detection Rate : 0.04651
Detection Prevalence : 0.48837
Balanced Accuracy : 0.74270
'Positive' Class : Yes
rf_results2 <- data.frame(as.factor(rf_pred), as.factor(valDataLabels$stroke), rf_prob)
colnames(rf_results2) <- c("pred", "obs", "No", "Yes")
rf_2class <- twoClassSummary(rf_results2, lev=c("No", "Yes"))
rf_2class[1]
ROC
0.8387227
rf_roc <- roc(valDataLabels$stroke ~ rf_prob$Yes, plot=TRUE, print.auc=TRUE, col='black', lwd=4, legacy.axes=TRUE, main="ROC Curve")
Setting levels: control = No, case = Yes
Setting direction: controls < cases

varImp(rf_model, scale = TRUE)
rf variable importance
rf_model
Random Forest
3271 samples
8 predictor
2 classes: 'No', 'Yes'
Pre-processing: re-scaling to [0, 1] (13)
Resampling: Cross-Validated (10 fold, repeated 5 times)
Summary of sample sizes: 2943, 2944, 2944, 2944, 2944, 2944, ...
Addtional sampling using ROSE prior to pre-processing
Resampling results across tuning parameters:
mtry ROC Sens Spec
1 0.8147439 0.539185011 0.92125
2 0.8100990 0.210691112 0.99125
4 0.7847946 0.004501195 1.00000
6 0.7651958 0.004501195 1.00000
8 0.7474645 0.004501195 1.00000
ROC was used to select the optimal model using the one SE rule.
The final value used for the model was mtry = 1.
plot(rf_model)

Linear SVM Model
set.seed(123)
lsvm_grid <- expand.grid(C = 3**(-7:7))
lsvm_data <- cbind(trainDataFeatures_e, trainDataLabels)
lsvm_model <- caret::train(stroke ~., data = lsvm_data, method = "svmLinear", trControl = myControl, preProcess = 'range', tuneGrid = lsvm_grid, metric= 'ROC')
lsvm_pred <- predict(lsvm_model, valDataFeatures_e)
lsvm_prob <- predict(lsvm_model, valDataFeatures_e, type='prob')
lsvm_results <- confusionMatrix(lsvm_pred, valDataLabels$stroke, positive="Yes")
lsvm_results2 <- data.frame(as.factor(lsvm_pred), as.factor(valDataLabels$stroke), lsvm_prob)
lsvm_results
Confusion Matrix and Statistics
Reference
Prediction No Yes
No 584 7
Yes 193 33
Accuracy : 0.7552
95% CI : (0.7242, 0.7843)
No Information Rate : 0.951
P-Value [Acc > NIR] : 1
Kappa : 0.1799
Mcnemar's Test P-Value : <2e-16
Sensitivity : 0.82500
Specificity : 0.75161
Pos Pred Value : 0.14602
Neg Pred Value : 0.98816
Prevalence : 0.04896
Detection Rate : 0.04039
Detection Prevalence : 0.27662
Balanced Accuracy : 0.78830
'Positive' Class : Yes
colnames(lsvm_results2) <- c("pred", "obs", "No", "Yes")
lsvm_2class <- twoClassSummary(lsvm_results2, lev=c("No", "Yes"))
lsvm_2class[1]
ROC
0.8487773
varImp(lsvm_model, scale = TRUE)
ROC curve variable importance
lsvm_roc <- roc(valDataLabels$stroke ~ lsvm_prob$Yes, plot=TRUE, print.auc=TRUE, col='black', lwd=4, legacy.axes=TRUE, main="ROC Curve", add=FALSE)
Setting levels: control = No, case = Yes
Setting direction: controls < cases

plot(lsvm_model, scales=list(x=list(log=3)))

lsvm_model
Support Vector Machines with Linear Kernel
3271 samples
15 predictor
2 classes: 'No', 'Yes'
Pre-processing: re-scaling to [0, 1] (15)
Resampling: Cross-Validated (10 fold, repeated 5 times)
Summary of sample sizes: 2943, 2944, 2944, 2944, 2944, 2944, ...
Addtional sampling using ROSE prior to pre-processing
Resampling results across tuning parameters:
C ROC Sens Spec
4.572474e-04 0.8223611 0.7261314 0.77000
1.371742e-03 0.8305492 0.7266456 0.79875
4.115226e-03 0.8371825 0.7276736 0.81625
1.234568e-02 0.8363893 0.7294760 0.80875
3.703704e-02 0.8366878 0.7251682 0.81625
1.111111e-01 0.8377090 0.7283768 0.80875
3.333333e-01 0.8391237 0.7261312 0.81250
1.000000e+00 0.8384358 0.7249744 0.81375
3.000000e+00 0.8372659 0.7269680 0.81125
9.000000e+00 0.8369120 0.7234337 0.80875
2.700000e+01 0.8387552 0.7233682 0.81625
8.100000e+01 0.8389665 0.7263268 0.81625
2.430000e+02 0.8384265 0.7249740 0.81625
7.290000e+02 0.8341659 0.7413742 0.78125
2.187000e+03 0.8366235 0.7315947 0.81000
ROC was used to select the optimal model using the one SE rule.
The final value used for the model was C = 0.004115226.
Gradient Boost Model
gbm_Grid <- expand.grid(interaction.depth = c(1, 3, 6, 9, 10),
n.trees = seq(250,2000,250),
shrinkage = seq(.0005, .05,.0005),
n.minobsinnode = 10)
set.seed(123)
gbm_data = cbind(trainDataFeatures_e, trainDataLabels)
gbm_model <- caret::train(stroke ~., data = gbm_data, method = "gbm", trControl = myControl, preProcess = 'range', metric= 'ROC', tuneLength = 10)
gbm_pred <- predict(gbm_model, valDataFeatures_e)
gbm_prob <- predict(gbm_model, valDataFeatures_e, type='prob')
gbm_results <- confusionMatrix(gbm_pred, valDataLabels$stroke, positive='Yes')
gbm_results
Confusion Matrix and Statistics
Reference
Prediction No Yes
No 3 0
Yes 774 40
Accuracy : 0.0526
95% CI : (0.0383, 0.0702)
No Information Rate : 0.951
P-Value [Acc > NIR] : 1
Kappa : 4e-04
Mcnemar's Test P-Value : <2e-16
Sensitivity : 1.000000
Specificity : 0.003861
Pos Pred Value : 0.049140
Neg Pred Value : 1.000000
Prevalence : 0.048960
Detection Rate : 0.048960
Detection Prevalence : 0.996328
Balanced Accuracy : 0.501931
'Positive' Class : Yes
gbm_results2 <- data.frame(as.factor(gbm_pred), as.factor(valDataLabels$stroke), gbm_prob)
colnames(gbm_results2) <- c("pred", "obs", "No", "Yes")
gbm_2class <- twoClassSummary(gbm_results2, lev=c("No", "Yes"))
gbm_2class[1]
ROC
0.6796493
gbm_roc <- roc(valDataLabels$stroke ~ gbm_prob$Yes, plot=TRUE, print.auc=TRUE, col='black', lwd=4, legacy.axes=TRUE, main="ROC Curve", add=FALSE)
Setting levels: control = No, case = Yes
Setting direction: controls < cases

gbm_model
Stochastic Gradient Boosting
3271 samples
15 predictor
2 classes: 'No', 'Yes'
Pre-processing: re-scaling to [0, 1] (15)
Resampling: Cross-Validated (10 fold, repeated 5 times)
Summary of sample sizes: 2943, 2944, 2944, 2944, 2944, 2944, ...
Addtional sampling using ROSE prior to pre-processing
Resampling results across tuning parameters:
interaction.depth n.trees ROC Sens Spec
1 50 0.5120476 0.01311856 0.99625
1 100 0.5120396 0.01614107 0.99500
1 150 0.5284698 0.01311856 0.99625
1 200 0.5718248 0.01909927 0.99375
1 250 0.5915129 0.01909927 0.99375
1 300 0.6157874 0.01607676 0.99500
1 350 0.6248188 0.01607676 0.99500
1 400 0.6370812 0.01607676 0.99500
1 450 0.6420189 0.01909927 0.99375
1 500 0.6442202 0.01607676 0.99500
2 50 0.5164203 0.01909927 0.99375
2 100 0.5623875 0.01909927 0.99375
2 150 0.5850932 0.01909927 0.99375
2 200 0.5986393 0.01909927 0.99375
2 250 0.6160540 0.01916357 0.99375
2 300 0.6317473 0.01922788 0.99375
2 350 0.6487803 0.01646261 0.99500
2 400 0.6537964 0.01652692 0.99500
2 450 0.6606227 0.01652692 0.99500
2 500 0.6647613 0.01652692 0.99500
3 50 0.5114126 0.01909927 0.99375
3 100 0.5378068 0.01909927 0.99375
3 150 0.5664027 0.01909927 0.99375
3 200 0.5855627 0.01909927 0.99375
3 250 0.5919699 0.01916357 0.99375
3 300 0.6096614 0.01916357 0.99375
3 350 0.6172812 0.01916357 0.99375
3 400 0.6210599 0.01929219 0.99375
3 450 0.6283093 0.01929219 0.99375
3 500 0.6380882 0.01929219 0.99375
4 50 0.5192763 0.01909927 0.99375
4 100 0.5415156 0.01909927 0.99375
4 150 0.5633766 0.01909927 0.99375
4 200 0.5706053 0.01909927 0.99375
4 250 0.5790300 0.01909927 0.99375
4 300 0.5897672 0.01909927 0.99375
4 350 0.6253183 0.01922788 0.99375
4 400 0.6463812 0.01922788 0.99375
4 450 0.6583945 0.01922788 0.99375
4 500 0.6624801 0.01922788 0.99375
5 50 0.4982885 0.01909927 0.99375
5 100 0.5213421 0.01909927 0.99375
5 150 0.5531265 0.01909927 0.99375
5 200 0.5872584 0.01909927 0.99375
5 250 0.6011820 0.01909927 0.99375
5 300 0.6084796 0.01967660 0.99375
5 350 0.6348726 0.01993301 0.99375
5 400 0.6606058 0.02006122 0.99375
5 450 0.6667146 0.02044666 0.99375
5 500 0.6759749 0.02057527 0.99375
6 50 0.5229865 0.01909927 0.99375
6 100 0.5431204 0.01909927 0.99375
6 150 0.5414926 0.01909927 0.99375
6 200 0.5618280 0.01909927 0.99375
6 250 0.5709914 0.01909927 0.99375
6 300 0.5918941 0.01916357 0.99375
6 350 0.6107437 0.01916357 0.99375
6 400 0.6242674 0.01942081 0.99375
6 450 0.6414063 0.02173592 0.99375
6 500 0.6391775 0.02135007 0.99375
7 50 0.4739808 0.01909927 0.99375
7 100 0.4692592 0.01909927 0.99375
7 150 0.4962372 0.01909927 0.99375
7 200 0.5336867 0.01909927 0.99375
7 250 0.5601293 0.02135007 0.99375
7 300 0.5752217 0.02154300 0.99375
7 350 0.5990514 0.02212177 0.99375
7 400 0.6127874 0.02270055 0.99375
7 450 0.6220114 0.02334364 0.99375
7 500 0.6380094 0.02372949 0.99375
8 50 0.4832537 0.01909927 0.99375
8 100 0.5032918 0.01909927 0.99375
8 150 0.5346193 0.01909927 0.99375
8 200 0.5394732 0.01916357 0.99375
8 250 0.5726730 0.01935650 0.99375
8 300 0.5902557 0.01980666 0.99375
8 350 0.6110737 0.01993528 0.99375
8 400 0.6203771 0.01999959 0.99375
8 450 0.6540952 0.02012821 0.99375
8 500 0.6631274 0.02019251 0.99375
9 50 0.5291433 0.01909927 0.99375
9 100 0.5304313 0.01909927 0.99375
9 150 0.5330051 0.01909927 0.99375
9 200 0.5486256 0.01909927 0.99375
9 250 0.5942750 0.02295779 0.99375
9 300 0.6119054 0.02398673 0.99375
9 350 0.6214457 0.02572079 0.99375
9 400 0.6257709 0.02655578 0.99375
9 450 0.6386572 0.02668419 0.99375
9 500 0.6522057 0.02674829 0.99375
10 50 0.5091538 0.01909927 0.99375
10 100 0.5384197 0.01909927 0.99375
10 150 0.5509515 0.01909927 0.99375
10 200 0.5710913 0.01909927 0.99375
10 250 0.5958467 0.01909927 0.99375
10 300 0.6124699 0.02077047 0.99375
10 350 0.6356838 0.02250495 0.99375
10 400 0.6526590 0.02282649 0.99375
10 450 0.6743980 0.02417615 0.99375
10 500 0.6833120 0.02417615 0.99375
Tuning parameter 'shrinkage' was held constant at a value of 0.1
Tuning parameter 'n.minobsinnode' was held constant at a value of 10
ROC was used to select the optimal model using the one SE rule.
The final values used for the model were n.trees = 450, interaction.depth = 10, shrinkage = 0.1 and n.minobsinnode = 10.
plot(gbm_model)

Neural Network Model
Scale datasets for usage
numerical <- c(1,6,7)
trainDataFullNum <- trainDataFull[numerical]
trainDataFullCat <- trainDataFull[-numerical]
trainDataNum <- trainData[numerical]
trainDataCat <- trainData[-numerical]
valDataNum <- valData[numerical]
valDataCat <- valData[-numerical]
testDataNum <- testData[numerical]
testDataCat <- testData[-numerical]
trainDataFullScaled <- scale(trainDataFullNum)
col_means_train <- attr(trainDataFullScaled, "scaled:center")
col_stddevs_train <- attr(trainDataFullScaled, "scaled:scale")
testDataScaled <- scale(testDataNum, center = col_means_train, scale = col_stddevs_train)
trainDataScaled <- scale(trainDataNum)
col_means_train <- attr(trainDataScaled, "scaled:center")
col_stddevs_train <- attr(trainDataScaled, "scaled:scale")
valDataScaled <- scale(valDataNum, center = col_means_train, scale = col_stddevs_train)
nnFullTrainData <- cbind(trainDataFullScaled, trainDataFullCat)
nnTrainData <- cbind(trainDataScaled, trainDataCat)
nnTestData <- cbind(testDataScaled, testDataCat)
nnValData <- cbind(valDataScaled, valDataCat)
# balance training data
nnTrainData <- ROSE(stroke ~ ., data=nnTrainData, p=0.5)$data
nnFullTrainData <- ROSE(stroke ~ ., data=nnFullTrainData, p=0.5)$data
nnValBal <- ROSE(stroke ~ ., data=nnValData, p=0.5)$data
separate features from labels
nnFullTrainFeatures <- nnFullTrainData[-9]
nnFullTrainLabels <- nnFullTrainData[9]
nnTrainFeatures <- nnTrainData[-9]
nnTrainLabels <- nnTrainData[9]
nnTestFeatures <- nnTestData[-9]
nnTestLabels <- nnTestData[9]
nnValFeatures <- nnValData[-9]
nnValLabels <- nnValData[9]
nnValBalFeatures <- nnValBal[-9]
nnValBalLabels <- nnValBal[9]
nnValLabels$stroke <- ifelse(nnValLabels$stroke == 'No', 0, 1)
nnTestLabels$stroke <- ifelse(nnTestLabels$stroke == 'No', 0, 1)
nnTrainLabels$stroke <- ifelse(nnTrainLabels$stroke == 'No', 0, 1)
nnFullTrainLabels$stroke <- ifelse(nnFullTrainLabels$stroke == 'No', 0, 1)
nnValBalLabels$stroke <- ifelse(nnValBalLabels$stroke == 'No', 0, 1)
### Stroke Distrubution for Train Dataset
-------------
0 1
------ ------
1642 1629
-------------
### Stroke Distrubution for fullTrain Dataset
-------------
0 1
------ ------
2021 2067
-------------
one-hot encode categorical features
dummyFullTrain <- dummyVars("~ .", data=nnFullTrainFeatures)
dummyTrain <- dummyVars("~ .", data=nnTrainFeatures)
nnFullTrainFeatures <- data.frame(predict(dummyFullTrain,newdata=nnFullTrainFeatures))
nnTestFeatures <- data.frame(predict(dummyFullTrain,newdata=nnTestFeatures))
nnTrainFeatures <- data.frame(predict(dummyTrain,newdata=nnTrainFeatures))
nnValFeatures <- data.frame(predict(dummyTrain,newdata=nnValFeatures))
nnValBalFeatures <- data.frame(predict(dummyTrain,newdata=nnValBalFeatures))
Neural Network Model Tuning
clean_runs(confirm=FALSE)
set.seed(123)
runs <- tuning_run("ann.R", flags = list(nodes1 = c(20, 50,75), nodes2=c(20,50,75), drop1=c(0.2,0.4,0.6), drop2=c(0.2,0.4,0.6), learning_rate = c(0.01,0.05,0.001,0.0001), epochs = c(25,50,100), batch_size = c(10,25,50), activation = c("relu", "sigmoid", "tanh")),sample = 0.02, confirm=FALSE, echo = FALSE)
rundf <- ls_runs(order='metric_val_accuracy', decreasing=TRUE)
rundf
Data frame: 175 x 28
# ... with 165 more rows
# ... with 23 more columns:
# flag_nodes1, flag_nodes2, flag_batch_size, flag_activation, flag_learning_rate,
# flag_epochs, flag_drop1, flag_drop2, epochs, epochs_completed, metrics, model,
# loss_function, optimizer, learning_rate, script, start, end, completed, output,
# source_code, context, type
bestrun <- rundf[1,1:19]
Best Run Details
| run_dir |
runs/2021-04-28T05-06-54Z |
| metric_val_accuracy |
0.951 |
| metric_loss |
0.6952 |
| metric_accuracy |
0.5032 |
| metric_val_loss |
0.6749 |
| flag_nodes1 |
50 |
| flag_nodes2 |
50 |
| flag_batch_size |
10 |
| flag_activation |
relu |
| flag_learning_rate |
0.05 |
| flag_epochs |
50 |
| flag_drop1 |
0.4 |
| flag_drop2 |
0.6 |
| epochs |
50 |
| epochs_completed |
50 |
| metrics |
runs/2021-04-28T05-06-54Z/tfruns.d/metrics.json |
| model |
Model Model: “sequential” ________________________________________________________________________________ Layer (type) Output Shape Param # ================================================================================ dense_2 (Dense) (None, 50) 800 ________________________________________________________________________________ dropout_1 (Dropout) (None, 50) 0 ________________________________________________________________________________ dense_1 (Dense) (None, 50) 2550 ________________________________________________________________________________ dropout (Dropout) (None, 50) 0 ________________________________________________________________________________ dense (Dense) (None, 1) 51 ================================================================================ Total params: 3,401 Trainable params: 3,401 Non-trainable params: 0 ________________________________________________________________________________ |
| loss_function |
binary_crossentropy |
| optimizer |
<tensorflow.python.keras.optimizer_v2.adam.Adam> |
Create model using paramters from best tuning run
model <- keras_model_sequential()
model %>%
layer_dense(units = 50, activation = "relu", input_shape = dim(nnTrainFeatures)[2]) %>%
layer_dropout(rate=0.4) %>%
layer_dense(units = 50, activation = "relu") %>%
layer_dropout(rate=0.6) %>%
layer_dense(units = 1, activation = 'sigmoid')
model %>% compile(
optimizer = optimizer_adam(lr=0.05),
loss='binary_crossentropy',
metrics = c('accuracy')
)
set.seed(234)
history <- model %>% fit(as.matrix(nnTrainFeatures), as.matrix(nnTrainLabels), epochs=50, batch_size=10,validation_data=list(as.matrix(nnValFeatures), as.matrix(nnValLabels)))
Evaluate NN Model Performance
set.seed(123)
model %>% evaluate(as.matrix(nnValFeatures), as.matrix(nnValLabels))
1/26 [>.............................] - ETA: 0s - loss: 0.4610 - accuracy: 0.5000
26/26 [==============================] - 0s 627us/step - loss: 0.4428 - accuracy: 0.4908
loss
26/26 [==============================] - 0s 634us/step - loss: 0.4428 - accuracy: 0.4908
accuracy
0.4427884 0.4908201
nn_pred <- model %>% predict_classes(as.matrix(nnValFeatures))
nn_prob <- model %>% predict_proba(as.matrix(nnValFeatures))
nn_prob <- data.frame(nn_prob)
colnames(nn_prob) <- c('Yes')
nn_prob$No <- 1 - nn_prob$Yes
nn_results2 <- data.frame(nn_pred, nnValLabels, nn_prob)
colnames(nn_results2) <- c("pred", "obs", "Yes", "No")
nn_results2$pred <-as.factor(ifelse(nn_results2$pred == 0, 'No', 'Yes'))
nn_results2$obs <-as.factor(ifelse(nn_results2$obs == 0, 'No', 'Yes'))
nn_2class <- twoClassSummary(nn_results2, lev=c("No", "Yes"))
confusionMatrix(nn_results2$pred,nn_results2$obs, positive='Yes')
Confusion Matrix and Statistics
Reference
Prediction No Yes
No 361 0
Yes 416 40
Accuracy : 0.4908
95% CI : (0.456, 0.5257)
No Information Rate : 0.951
P-Value [Acc > NIR] : 1
Kappa : 0.0783
Mcnemar's Test P-Value : <2e-16
Sensitivity : 1.00000
Specificity : 0.46461
Pos Pred Value : 0.08772
Neg Pred Value : 1.00000
Prevalence : 0.04896
Detection Rate : 0.04896
Detection Prevalence : 0.55814
Balanced Accuracy : 0.73230
'Positive' Class : Yes
nn_2class[1]
ROC
0.7342342
plot(history)

nn_roc <- roc(nnValLabels$stroke ~ nn_prob$Yes, plot=TRUE, print.auc=TRUE, col='black', lwd=4, legacy.axes=TRUE, main="ROC Curve", add=FALSE)
Setting levels: control = 0, case = 1
Setting direction: controls < cases

Model Comparison
Model Performace Comparison
| knn |
0.7889 |
0.7773 |
0.625 |
| glmnet |
0.8555 |
0.7091 |
0.85 |
| rpart |
0.7064 |
0.9936 |
0.1 |
| rf |
0.8387 |
0.5354 |
0.95 |
| svmLinear |
0.8488 |
0.7516 |
0.825 |
| gbm |
0.6796 |
0.003861 |
1 |
| neural network |
0.7342 |
0.4646 |
1 |
pred_rf= prediction(rf_prob$Yes,valDataLabels$stroke)
performance(pred_rf, measure = "auc")@y.values
rf_roc <- roc(valDataLabels$stroke ~ rf_prob$Yes, plot=TRUE, print.auc=FALSE, col='green', lwd=4, legacy.axes=TRUE, main="ROC Curve")
pred_knn = prediction(knn_prob$Yes, valDataLabels$stroke)
performance(pred_knn, measure='auc')@y.values
knn_roc <- roc(valDataLabels$stroke ~ knn_prob$Yes, plot=TRUE, print.auc=FALSE, col='blue', lwd=4, legacy.axes=TRUE, main="ROC Curve", add=TRUE)
pred_glm <- prediction(glm_prob$Yes, valDataLabels$stroke)
glm_perform <- performance(pred_glm, measure='auc')@y.values
glm_roc <- roc(valDataLabels$stroke ~ glm_prob$Yes, plot=TRUE, print.auc=FALSE, col='orange', lwd=4, legacy.axes=TRUE, main="ROC Curve", add=TRUE)
pred_tree <- prediction(tree_prob$Yes, valDataLabels$stroke)
performance(pred_tree, measure='auc')@y.values
tree_roc <- roc(valDataLabels$stroke ~ tree_prob$Yes, plot=TRUE, print.auc=FALSE, col='yellow', lwd=4, legacy.axes=TRUE, main="ROC Curve", add=TRUE)
pred_lsvm <- prediction(lsvm_prob$Yes, valDataLabels$stroke)
performance(pred_lsvm, measure='auc')@y.values
lsvm_roc <- roc(valDataLabels$stroke ~ lsvm_prob$Yes, plot=TRUE, print.auc=FALSE, col='purple', lwd=4, legacy.axes=TRUE, main="ROC Curve", add=TRUE)
pred_gbm <- prediction(gbm_prob$Yes, valDataLabels$stroke)
performance(pred_gbm, measure='auc')@y.values
gbm_roc <- roc(valDataLabels$stroke ~ gbm_prob$Yes, plot=TRUE, print.auc=FALSE, col='black', lwd=4, legacy.axes=TRUE, main="ROC Curve", add=TRUE)
pred_nn = prediction(nn_prob$Yes,nnValLabels$stroke)
performance(pred_nn, measure='auc')@y.values
nn_roc <- roc(nnValLabels$stroke ~ nn_prob$Yes, plot=TRUE, print.auc=FALSE, col='brown', lwd=4, legacy.axes=TRUE, main="ROC Curve", add=TRUE)
legend("bottomright", legend=c("knn", 'rf', 'glmnet', 'rpart', 'svmLinear', 'gbm', 'neural network'), col=c("blue", "green", 'orange', 'yellow', 'purple', 'black', 'brown'), lwd=2)

LS0tCnRpdGxlOiAiU3Ryb2tlIFByZXZlbnRpb24iCnN1YnRpdGxlOiAiQ1NDNTMyQiBTcHJpbmcgMjAyMSIKYXV0aG9yOiAiVGhvbWFzIENoYW1waW9uIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpgYGB7ciBpbmNsdWRlPUZBTFNFfQpyZXF1aXJlKHBseXIpCnJlcXVpcmUoZHBseXIpCnJlcXVpcmUocGFuZGVyKQpyZXF1aXJlKGdncGxvdDIpCnJlcXVpcmUoZ3JpZEV4dHJhKQpyZXF1aXJlKGNhcmV0KQpyZXF1aXJlKERNd1IpCnJlcXVpcmUoUk9TRSkKcmVxdWlyZShrZXJhcykKdXNlX3Nlc3Npb25fd2l0aF9zZWVkKDQyKQpyZXF1aXJlKHRlbnNvcmZsb3cpCnJlcXVpcmUodGZydW5zKQpyZXF1aXJlKFJPQ1IpCnJlcXVpcmUocFJPQykKcmVxdWlyZShPbmVSKQpyZXF1aXJlKGdsbW5ldCkKcmVxdWlyZShyYXR0bGUpCnJlcXVpcmUocnBhcnQpCnJlcXVpcmUocnBhcnQucGxvdCkKcmVxdWlyZShnYm0pCnJlcXVpcmUoeGdib29zdCkKYGBgCgojIExvYWQgYW5kIEV4cGxvcmUgRGF0YQpgYGB7cn0Kc3Ryb2tlcyA8LSByZWFkLmNzdigiLi9kYXRhL3N0cm9rZV9kYXRhLmNzdiIpCnN0cihzdHJva2VzKQpzdW1tYXJ5KHN0cm9rZXMpCmBgYAoKCgoKIyMjIFZlcmlmeSB0aGF0IHRoZSBJRCBjb2x1bW4gaXMgaW4gZmFjdCB1bmlxdWUKIyMjIFJlbW92ZSBJRCBjb2x1bW4gc2luY2UgdGhpcyBpcyBhbiBhc3NpZ25lZCBpZGVudGlmaWVyIGFuZCBhcyBzdWNoIGRvZXMgbm90IGltcGFjdCBzdHJva2UKCmBgYHtyfQp1bmlxdWVJRCA8LSB1bmlxdWUoc3Ryb2tlcyRpZCkKbGVuZ3RoKHVuaXF1ZUlEKQpkaW0oc3Ryb2tlcykKc3Ryb2tlcyA8LSBzdHJva2VzWy0xXQpgYGAKCiMjIyBTaW5jZSBhbGwgdGhlIHBvc2l0aXZlIHN0cm9rZSB2YWx1ZXMgYXJlIGZvdW5kIGF0IHRoZSBzdGFydCBvZiB0aGUgZmlsZSB3ZSB3aWxsIHJhbmRvbWl6ZSB0aGUgZGF0YWZyYW1lCgpgYGB7cn0Kc2V0LnNlZWQoNDIpCnJvd3MgPC0gc2FtcGxlKG5yb3coc3Ryb2tlcykpCnN0cm9rZXMgPC0gc3Ryb2tlc1tyb3dzLF0KYGBgCgojIyMgUmV2aWV3IGNhdGVnb3J5IGRpc3RydWJ1dGlvbiBmb3IgY2F0ZWdvcmljYWwgdmFyaWFibGVzCmBgYHtyIGVjaG89RkFMU0UsIHJlc3VsdHM9J2FzaXMnfQpwYW5kb2MuaGVhZGVyKCJHZW5kZXIgRW50cmllcyIsNSkKcGFuZG9jLnRhYmxlKHRhYmxlKHN0cm9rZXMkZ2VuZGVyKSkKCnBhbmRvYy5oZWFkZXIoIkV2ZXIgTWFycmllZCBFbnRyaWVzIiw1KQpwYW5kb2MudGFibGUodGFibGUoc3Ryb2tlcyRldmVyX21hcnJpZWQpKQoKcGFuZG9jLmhlYWRlcigiV29yayBUeXBlIEVudHJpZXMiLDUpCnBhbmRvYy50YWJsZSh0YWJsZShzdHJva2VzJHdvcmtfdHlwZSkpCgpwYW5kb2MuaGVhZGVyKCJSZXNpZGVuY2UgVHlwZUVudHJpZXMiLDUpCnBhbmRvYy50YWJsZSh0YWJsZShzdHJva2VzJFJlc2lkZW5jZV90eXBlKSkKCnBhbmRvYy5oZWFkZXIoIlNtb2tpbmcgU3RhdHVzIEVudHJpZXMiLDUpCnBhbmRvYy50YWJsZSh0YWJsZShzdHJva2VzJHNtb2tpbmdfc3RhdHVzKSkKCnBhbmRvYy5oZWFkZXIoIkh5cGVydGVuc2lvbkVudHJpZXMiLDUpCnBhbmRvYy50YWJsZSh0YWJsZShzdHJva2VzJGh5cGVydGVuc2lvbikpCgpwYW5kb2MuaGVhZGVyKCJIZWFydCBEaXNlYXNlIEVudHJpZXMiLDUpCnBhbmRvYy50YWJsZSh0YWJsZShzdHJva2VzJGhlYXJ0X2Rpc2Vhc2UpKQoKCgpwYW5kb2MuaGVhZGVyKCJTbW9raW5nIFN0YXR1cyBFbnRyaWVzIiw1KQpwYW5kb2MudGFibGUodGFibGUoc3Ryb2tlcyRzbW9raW5nX3N0YXR1cykpCgpwYW5kb2MuaGVhZGVyKCJTdHJva2UgRW50cmllcyIsNSkKcGFuZG9jLnRhYmxlKHRhYmxlKHN0cm9rZXMkc3Ryb2tlKSkKCmBgYAojIyMgVXBkYXRlIGJpbmFyeSBjYXRlZ29yaWNhbCB2YXJpYWJsZXMgdG8gbnVtZXJpYwojIyMgcmVtb3ZlIGdlbmRlciB3aXRoIHZhbHVlIG9mICdvdGhlcicsIGNvZGUgIkZlbWFsZSBhcyAwLCBtYWxlIGFzIDEKYGBge3J9CnN0cm9rZXMgPC0gc3Ryb2tlcyAlPiUgZmlsdGVyKGdlbmRlciAhPSAiT3RoZXIiKQpzdHJva2VzJGdlbmRlciA8LSBpZmVsc2Uoc3Ryb2tlcyRnZW5kZXIgPT0gIkZlbWFsZSIsIDAsMSkKYGBgCgojIyMgY29udmVydCByZXNpZGVuY2UgdHlwZSB0byBiaW5hcnkgcmVzcG9uc2UuIDA9cnVyYWwsIDE9dXJiYW4KYGBge3J9CnN0cm9rZXMkUmVzaWRlbmNlX3R5cGUgPSBpZmVsc2Uoc3Ryb2tlcyRSZXNpZGVuY2VfdHlwZSA9PSAiUnVyYWwiLCAwLCAxKQpgYGAKCiMjIyBjb252ZXJ0IGV2ZXIgbWFycmllZCB0byBiaW5hcnkgcmVzcG9uc2UuIDA9Tm8sIDE9WWVzCmBgYHtyfQpzdHJva2VzJGV2ZXJfbWFycmllZCA8LSBpZmVsc2Uoc3Ryb2tlcyRldmVyX21hcnJpZWQgPT0gJ05vJywgMCwxKQpgYGAKCgojIyMgQ29udmVydCBzdHJva2UgdG8gJ3llcy9ubycgdmFsdWVzCgpgYGB7cn0Kc3Ryb2tlcyRzdHJva2UgPC0gaWZlbHNlKHN0cm9rZXMkc3Ryb2tlID09IDEsICJZZXMiLCAiTm8iKQpgYGAKCgojIyMgQ29udmVydCBtdWx0aS1jYXRlZ29yeSBjYXRlZ29yaWNhbCBmZWF0dXJlcyB0byBmYWN0b3JzCmBgYHtyfQojIHN0cm9rZXMkZ2VuZGVyIDwtIGFzLmZhY3RvcihzdHJva2VzJGdlbmRlcikKIyBzdHJva2VzJGh5cGVydGVuc2lvbiA8LSBhcy5mYWN0b3Ioc3Ryb2tlcyRoeXBlcnRlbnNpb24pCiMgc3Ryb2tlcyRoZWFydF9kaXNlYXNlIDwtIGFzLmZhY3RvcihzdHJva2VzJGhlYXJ0X2Rpc2Vhc2UpCiMgc3Ryb2tlcyRldmVyX21hcnJpZWQgPC0gYXMuZmFjdG9yKHN0cm9rZXMkZXZlcl9tYXJyaWVkKQpzdHJva2VzJHdvcmtfdHlwZSA8LSBhcy5mYWN0b3Ioc3Ryb2tlcyR3b3JrX3R5cGUpCiMgc3Ryb2tlcyRSZXNpZGVuY2VfdHlwZSA8LSBhcy5mYWN0b3Ioc3Ryb2tlcyRSZXNpZGVuY2VfdHlwZSkKc3Ryb2tlcyRzbW9raW5nX3N0YXR1cyA8LSBhcy5mYWN0b3Ioc3Ryb2tlcyRzbW9raW5nX3N0YXR1cykKc3Ryb2tlcyRzdHJva2UgPC0gYXMuZmFjdG9yKHN0cm9rZXMkc3Ryb2tlKQpzdHJva2VzJHNtb2tpbmdfc3RhdHVzIDwtIHJldmFsdWUoc3Ryb2tlcyRzbW9raW5nX3N0YXR1cywgYygiZm9ybWVybHkgc21va2VkIj0iZm9ybWVybHlfc21va2VkIiwgIm5ldmVyIHNtb2tlZCI9Im5ldmVyX3Ntb2tlZCIsICJzbW9rZXMiPSJzbW9rZXMiLCAiVW5rbm93biI9InVua25vd24iKSkKc3Ryb2tlcyR3b3JrX3R5cGUgPC0gcmV2YWx1ZShzdHJva2VzJHdvcmtfdHlwZSwgYygiU2VsZi1lbXBsb3llZCI9IlNlbGZfZW1wbG95ZWQiKSkKCmBgYAoKCiMjIyBDbGVhbiBibWkgZmVhdHVyZSAgIAoqIENvbnZlcnQgIk4vQSIgZW50cmllcyB0byBOQSB2YWx1ZXMKKiBDb252ZXJ0IGZyb20gY2hhcmFjdGVyIHRvIG51bWVyaWMgdmFsdWVzCmBgYHtyfQpzdHJva2VzIDwtIG5hX2lmKHN0cm9rZXMsIk4vQSIpCnN0cm9rZXMkYm1pIDwtIGFzLm51bWVyaWMoc3Ryb2tlcyRibWkpCmRpbShzdHJva2VzKQoKYGBgCiMjIERhdGEgRXhwbG9yYXRpb24KYGBge3IgZWNobz1GQUxTRSxyZXN1bHRzPSdhc2lzJ30KbWlzc2luZyA8LSBzYXBwbHkoc3Ryb2tlcywgZnVuY3Rpb24oeCkgc3VtKGlzLm5hKHgpKSkKcGFuZG9jLmhlYWRlcigiTWlzc2luZyBWYWx1ZXMiLCA0KQpwYW5kb2MudGFibGUobWlzc2luZykKYGBgCmBgYHtyfQpyZXF1aXJlKHRpZHl2ZXJzZSkKcmVxdWlyZShyY29tcGFuaW9uKQoKCiMgQ2FsY3VsYXRlIGEgcGFpcndpc2UgYXNzb2NpYXRpb24gYmV0d2VlbiBhbGwgdmFyaWFibGVzIGluIGEgZGF0YS1mcmFtZS4gSW4gcGFydGljdWxhciBub21pbmFsIHZzIG5vbWluYWwgd2l0aCBDaGktc3F1YXJlLCBudW1lcmljIHZzIG51bWVyaWMgd2l0aCBQZWFyc29uIGNvcnJlbGF0aW9uLCBhbmQgbm9taW5hbCB2cyBudW1lcmljIHdpdGggQU5PVkEuCiMgQWRvcHRlZCBmcm9tIGh0dHBzOi8vc3RhY2tvdmVyZmxvdy5jb20vYS81MjU1NzYzMS81OTA0MzcKbWl4ZWRfYXNzb2MgPSBmdW5jdGlvbihkZiwgY29yX21ldGhvZD0ic3BlYXJtYW4iLCBhZGp1c3RfY3JhbWVyc3ZfYmlhcz1UUlVFKXsKICAgIGRmX2NvbWIgPSBleHBhbmQuZ3JpZChuYW1lcyhkZiksIG5hbWVzKGRmKSwgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKSAlPiUgc2V0X25hbWVzKCJYMSIsICJYMiIpCgogICAgaXNfbm9taW5hbCA9IGZ1bmN0aW9uKHgpIGNsYXNzKHgpICVpbiUgYygiZmFjdG9yIiwgImNoYXJhY3RlciIpCiAgICAjIGh0dHBzOi8vY29tbXVuaXR5LnJzdHVkaW8uY29tL3Qvd2h5LWlzLXB1cnItaXMtbnVtZXJpYy1kZXByZWNhdGVkLzM1NTkKICAgICMgaHR0cHM6Ly9naXRodWIuY29tL3ItbGliL3JsYW5nL2lzc3Vlcy83ODEKICAgIGlzX251bWVyaWMgPC0gZnVuY3Rpb24oeCkgeyBpcy5pbnRlZ2VyKHgpIHx8IGlzX2RvdWJsZSh4KX0KCiAgICBmID0gZnVuY3Rpb24oeE5hbWUseU5hbWUpIHsKICAgICAgICB4ID0gIHB1bGwoZGYsIHhOYW1lKQogICAgICAgIHkgPSAgcHVsbChkZiwgeU5hbWUpCgogICAgICAgIHJlc3VsdCA9IGlmKGlzX25vbWluYWwoeCkgJiYgaXNfbm9taW5hbCh5KSl7CiAgICAgICAgICAgICMgdXNlIGJpYXMgY29ycmVjdGVkIGNyYW1lcnNWIGFzIGRlc2NyaWJlZCBpbiBodHRwczovL3JkcnIuaW8vY3Jhbi9yY29tcGFuaW9uL21hbi9jcmFtZXJWLmh0bWwKICAgICAgICAgICAgY3YgPSBjcmFtZXJWKGFzLmNoYXJhY3Rlcih4KSwgYXMuY2hhcmFjdGVyKHkpLCBiaWFzLmNvcnJlY3QgPSBhZGp1c3RfY3JhbWVyc3ZfYmlhcykKICAgICAgICAgICAgZGF0YS5mcmFtZSh4TmFtZSwgeU5hbWUsIGFzc29jPWN2LCB0eXBlPSJjcmFtZXJzViIpCgogICAgICAgIH1lbHNlIGlmKGlzX251bWVyaWMoeCkgJiYgaXNfbnVtZXJpYyh5KSl7CiAgICAgICAgICAgIGNvcnJlbGF0aW9uID0gY29yKHgsIHksIG1ldGhvZD1jb3JfbWV0aG9kLCB1c2U9ImNvbXBsZXRlLm9icyIpCiAgICAgICAgICAgIGRhdGEuZnJhbWUoeE5hbWUsIHlOYW1lLCBhc3NvYz1jb3JyZWxhdGlvbiwgdHlwZT0iY29ycmVsYXRpb24iKQoKICAgICAgICB9ZWxzZSBpZihpc19udW1lcmljKHgpICYmIGlzX25vbWluYWwoeSkpewogICAgICAgICAgICAjIGZyb20gaHR0cHM6Ly9zdGF0cy5zdGFja2V4Y2hhbmdlLmNvbS9xdWVzdGlvbnMvMTE5ODM1L2NvcnJlbGF0aW9uLWJldHdlZW4tYS1ub21pbmFsLWl2LWFuZC1hLWNvbnRpbnVvdXMtZHYtdmFyaWFibGUvMTI0NjE4IzEyNDYxOAogICAgICAgICAgICByX3NxdWFyZWQgPSBzdW1tYXJ5KGxtKHggfiB5KSkkci5zcXVhcmVkCiAgICAgICAgICAgIGRhdGEuZnJhbWUoeE5hbWUsIHlOYW1lLCBhc3NvYz1zcXJ0KHJfc3F1YXJlZCksIHR5cGU9ImFub3ZhIikKCiAgICAgICAgfWVsc2UgaWYoaXNfbm9taW5hbCh4KSAmJiBpc19udW1lcmljKHkpKXsKICAgICAgICAgICAgcl9zcXVhcmVkID0gc3VtbWFyeShsbSh5IH54KSkkci5zcXVhcmVkCiAgICAgICAgICAgIGRhdGEuZnJhbWUoeE5hbWUsIHlOYW1lLCBhc3NvYz1zcXJ0KHJfc3F1YXJlZCksIHR5cGU9ImFub3ZhIikKCiAgICAgICAgfWVsc2UgewogICAgICAgICAgICB3YXJuaW5nKHBhc3RlKCJ1bm1hdGNoZWQgY29sdW1uIHR5cGUgY29tYmluYXRpb246ICIsIGNsYXNzKHgpLCBjbGFzcyh5KSkpCiAgICAgICAgfQoKICAgICAgICAjIGZpbmFsbHkgYWRkIGNvbXBsZXRlIG9icyBudW1iZXIgYW5kIHJhdGlvIHRvIHRhYmxlCiAgICAgICAgcmVzdWx0ICU+JSBtdXRhdGUoY29tcGxldGVfb2JzX3BhaXJzPXN1bSghaXMubmEoeCkgJiAhaXMubmEoeSkpLCBjb21wbGV0ZV9vYnNfcmF0aW89Y29tcGxldGVfb2JzX3BhaXJzL2xlbmd0aCh4KSkgJT4lIHJlbmFtZSh4PXhOYW1lLCB5PXlOYW1lKQogICAgfQoKICAgICMgYXBwbHkgZnVuY3Rpb24gdG8gZWFjaCB2YXJpYWJsZSBjb21iaW5hdGlvbgogICAgbWFwMl9kZihkZl9jb21iJFgxLCBkZl9jb21iJFgyLCBmKQp9CmBgYAojIyMgQXNzb2NpYXRpb25zIGJldHdlZW4gZmVhdHVyZXMKYGBge3J9CmFzc29jaWF0aW9ucyA8LSBtaXhlZF9hc3NvYyhzdHJva2VzKQphc3NvY2lhdGlvbnMKYGBgCiMjIGJtaSBpbXB1dGF0aW9uCiMjIyMgYm1pIGhhcyBtdWx0aXBsZSBtaXNzaW5nIHZhbHVlcy4gSW5zdGVhZCBvZiB1c2luZyB0aGUgbWVhbiBvciBtZWRpYW4gb2YgdGhlIGVudGlyZSBkYXRhIHNldCB0byBpbXB1dGUgdGhlIG1pc3NpbmcgdmFsdWVzIGRldGVybWluZSBpZiBhIHNlY29uZCBmZWF0dXJlIGhhcyBhbiBhc3NvY2lhdGlvbiB3aXRoIGJtaQoKIyMjIGFzc29jaWF0aW9uIG9mIGJtaSB3aXRoIG90aGVyIHZhcmlhYmxlcwpgYGB7cn0KYm1pX2Fzc29jIDwtIGFzc29jaWF0aW9ucyAlPiUgZmlsdGVyKHkgPT0gImJtaSIpCmBgYAoKIyMjIFBsb3RzIG9mIGJtaSB2cyBvdGhlciBmZWF0dXJlcyAgIAoKYGBge3J9CiMgZ2dwbG90KGRhdGE9YXNzb2NpYXRpb25zLCBhZXMoeD14LCB5PXksIGZpbGw9YXNzb2MpKSArIGdlb21fdGlsZSgpICsgdGhlbWUoYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KGFuZ2xlPTkwLCB2anVzdD0wLjUsIGhqdXN0PTEpLCBheGlzLnRpdGxlID0gZWxlbWVudF9ibGFuaygpLCBwbG90LnRpdGxlPWVsZW1lbnRfdGV4dChoanVzdD0wLjUpKSArIGdndGl0bGUoIkFzc29jaWF0aW9uIEJldHdlZW4gRmVhdHVyZXMiKQpnZ3Bsb3QoZGF0YT1ibWlfYXNzb2MsIGFlcyh4PXgsIHk9eSwgZmlsbD1hc3NvYykpICsgZ2VvbV90aWxlKCkgKyB0aGVtZShheGlzLnRleHQueD1lbGVtZW50X3RleHQoYW5nbGU9OTAsIHZqdXN0PTAuNSwgaGp1c3Q9MSwgc2l6ZT0yMCksIGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KHNpemU9MjApLGF4aXMudGl0bGUgPSBlbGVtZW50X2JsYW5rKCksIHBsb3QudGl0bGU9ZWxlbWVudF90ZXh0KGhqdXN0PTAuNSkpICsgZ2d0aXRsZSgiQXNzb2NpYXRpb24gd2l0aCBibWkiKQpwbG90KGJtaSB+IC4sIGRhdGE9c3Ryb2tlcykKYGBgCgojIyMjIEFnZSBoYXMgYW4gYXNzb2NpYXRpb24gd2l0aCBibWksIHNvIHRoZSBkYXRhIHdpbGwgYmUgYmlubmVkIGJ5IGFnZSBhbmQgdGhlIG1pc3NpbmcgYm1pIHZhbHVlcyB3aWxsIGJlIGltcHV0ZWQgYmFzZWQgdXBvbiB0aGUgbWVkaWFuIHZhbHVlIG9mIHRoZSBiaW4gdGhleSBmYWxsIGluCgoKIyMjIENyZWF0ZSBiaW5zIGZvciBhZ2UgZmVhdHVyZSBhbmQgc2V0IG1pc3NpbmcgQk1JIHZhbHVlcyB0byBtZWRpYW4gb2YgdGhlIGJpbiB0aGV5IGZhbGwgaW4KYGBge3IgZWNobz1UUlVFfQpzdHJva2VzX29yaWdpbmFsIDwtIHN0cm9rZXMKc3Ryb2tlcyRhZ2VfY2F0IDwtIGJpbihzdHJva2VzJGFnZSwgbmJpbnM9MTAsIGxhYmVscz1jKCdhMScsICdhMicsICdhMycsICdhNCcsICdhNScsICdhNicsICdhNycsICdhOCcsJ2E5JywgJ2ExMCcpLCBtZXRob2QgPSAiY2x1c3RlcnMiKQoKc3Ryb2tlcyA8LSBzdHJva2VzICU+JSBncm91cF9ieShhZ2VfY2F0KSAlPiUgbXV0YXRlKGJtaS5uZXcgPSBtZWRpYW4oYm1pLCBuYS5ybT1UUlVFKSkKc3Ryb2tlcyRibWkgPC0gaWZlbHNlKGlzLm5hKHN0cm9rZXMkYm1pKSwgc3Ryb2tlcyRibWkubmV3LCBzdHJva2VzJGJtaSkKCiMgcmVtb3ZlIHdvcmtpbmcgY29sdW1ucwpzdHJva2VzIDwtIHN0cm9rZXNbLWMoMTIsMTMpXQpgYGAKCiMjIyBWZXJpZnkgbWlzc2luZyB2YWx1ZXMgYXJlIHJlc29sdmVkIGFuZCBkYXRhIHR5cGVzIGFyZSBhcyBleHBlY3RlZApgYGAge3IgZWNobz1GQUxTRSwgcmVzdWx0cz0nYXNpcyd9Cm1pc3NpbmcgPC0gc2FwcGx5KHN0cm9rZXMsIGZ1bmN0aW9uKHgpIHN1bShpcy5uYSh4KSkpCnBhbmRvYy5oZWFkZXIoIk1pc3NpbmcgVmFsdWVzIiwgNCkKcGFuZG9jLnRhYmxlKG1pc3NpbmcpCmBgYAoKYGBge3J9CgoKCnN0cihzdHJva2VzKQpgYGAKCiMjIyBFeHBsb3JlIHJlbGF0aW9uIG9mIGZlYXR1cmVzIHdpdGggcmVzcG9uc2UgdmFyaWFibGUgJ3N0cm9rZScKCgoKCmBgYHtyfQpwMSA8LSBnZ3Bsb3QoZGF0YT1zdHJva2VzLCBhZXMoc3Ryb2tlLCBmaWxsPWFzLmZhY3RvcihnZW5kZXIpKSkgKyBnZW9tX2Jhcihwb3NpdGlvbj0nZmlsbCcpICsgbGFicyhmaWxsPSJnZW5kZXIiKQpwMiA8LSBnZ3Bsb3QoZGF0YT1zdHJva2VzLCBhZXMoc3Ryb2tlLCBhZ2UpKSArIGdlb21fYm94cGxvdCgpCnAzIDwtIGdncGxvdChkYXRhPXN0cm9rZXMsIGFlcyhzdHJva2UsIGZpbGw9YXMuZmFjdG9yKGh5cGVydGVuc2lvbikpKSArIGdlb21fYmFyKHBvc2l0aW9uPSdmaWxsJykgKyBsYWJzKGZpbGw9Imh5cGVydGVuc2lvbiIpCnA0IDwtIGdncGxvdChkYXRhPXN0cm9rZXMsIGFlcyhzdHJva2UsIGZpbGw9YXMuZmFjdG9yKGhlYXJ0X2Rpc2Vhc2UpKSkgKyBnZW9tX2Jhcihwb3NpdGlvbj0nZmlsbCcpICsgbGFicyhmaWxsPSdoZWFydF9kaXNlYXNlJykKcDUgPC0gZ2dwbG90KGRhdGE9c3Ryb2tlcywgYWVzKHN0cm9rZSwgZmlsbD1hcy5mYWN0b3IoZXZlcl9tYXJyaWVkKSkpICsgZ2VvbV9iYXIocG9zaXRpb249J2ZpbGwnKSArIGxhYnMoZmlsbD0iZXZlcl9tYXJyaWVkIikKcDYgPC0gZ2dwbG90KGRhdGE9c3Ryb2tlcywgYWVzKHN0cm9rZSwgZmlsbD13b3JrX3R5cGUpKSArIGdlb21fYmFyKHBvc2l0aW9uPSdmaWxsJykKcDcgPC0gZ2dwbG90KGRhdGE9c3Ryb2tlcywgYWVzKHN0cm9rZSwgZmlsbD1hcy5mYWN0b3IoUmVzaWRlbmNlX3R5cGUpKSkgKyBnZW9tX2Jhcihwb3NpdGlvbj0nZmlsbCcpICsgbGFicyhmaWxsPSJSZXNpZGVuY2VfdHlwZSIpCnA4IDwtIGdncGxvdChkYXRhPXN0cm9rZXMsIGFlcyhzdHJva2UsIGF2Z19nbHVjb3NlX2xldmVsKSkgKyBnZW9tX2JveHBsb3QoKQpwOSA8LSBnZ3Bsb3QoZGF0YT1zdHJva2VzLCBhZXMoc3Ryb2tlLCBibWkpKSArIGdlb21fYm94cGxvdCgpCnAxMCA8LSBnZ3Bsb3QoZGF0YT1zdHJva2VzLCBhZXMoc3Ryb2tlLCBmaWxsPXNtb2tpbmdfc3RhdHVzKSkgKyBnZW9tX2Jhcihwb3NpdGlvbj0nZmlsbCcpCgpncmlkLmFycmFuZ2UocDEscDIscDMscDQsbnJvdz0yKQpncmlkLmFycmFuZ2UocDUscDYscDcscDgsbnJvdz0yKQpncmlkLmFycmFuZ2UocDkscDEwLG5yb3c9MSkKCmBgYApgYGB7ciBlY2hvPUZBTFNFLCByZXN1bHRzPSdhc2lzJ30KYWdlX3QgPC0gdC50ZXN0KHN0cm9rZXMkYWdlfnN0cm9rZXMkc3Ryb2tlLCBhbHRlcm5hdGl2ZT0ndHdvLnNpZGVkJykKZ2x1Y29zZV90IDwtIHQudGVzdChzdHJva2VzJGF2Z19nbHVjb3NlX2xldmVsfnN0cm9rZXMkc3Ryb2tlLCBhbHRlcm5hdGl2ZT0ndHdvLnNpZGVkJykKYm1pX3QgPC0gdC50ZXN0KHN0cm9rZXMkYm1pfnN0cm9rZXMkc3Ryb2tlLCBhbHRlcm5hdGl2ZT0ndHdvLnNpZGVkJykKCgpzbW9raW5nX2NoaTIgPC0gc3Ryb2tlcyAlPiUgc2VsZWN0KHNtb2tpbmdfc3RhdHVzLHN0cm9rZSkgJT4lIHRhYmxlKCkgJT4lIGNoaXNxLnRlc3QoKQpnZW5kZXJfY2hpMiA8LSBzdHJva2VzICU+JSBzZWxlY3QoZ2VuZGVyLHN0cm9rZSkgJT4lIHRhYmxlKCkgJT4lIGNoaXNxLnRlc3QoKQpoeXBlcnRlbnNpb25fY2hpMiA8LSBzdHJva2VzICU+JSBzZWxlY3QoaHlwZXJ0ZW5zaW9uLHN0cm9rZSkgJT4lIHRhYmxlKCkgJT4lIGNoaXNxLnRlc3QoKQpoZF9jaGkyIDwtIHN0cm9rZXMgJT4lIHNlbGVjdChoZWFydF9kaXNlYXNlLHN0cm9rZSkgJT4lIHRhYmxlKCkgJT4lIGNoaXNxLnRlc3QoKQplbV9jaGkyIDwtIHN0cm9rZXMgJT4lIHNlbGVjdChldmVyX21hcnJpZWQsc3Ryb2tlKSAlPiUgdGFibGUoKSAlPiUgY2hpc3EudGVzdCgpCnd0X2NoaTIgPC0gc3Ryb2tlcyAlPiUgc2VsZWN0KHdvcmtfdHlwZSxzdHJva2UpICU+JSB0YWJsZSgpICU+JSBjaGlzcS50ZXN0KCkKcnRfY2hpMiA8LSBzdHJva2VzICU+JSBzZWxlY3QoUmVzaWRlbmNlX3R5cGUsc3Ryb2tlKSAlPiUgdGFibGUoKSAlPiUgY2hpc3EudGVzdCgpCgphZ2VwIDwtIGFnZV90JHAudmFsdWUKZ2x1Y29zcCA8LSBnbHVjb3NlX3QkcC52YWx1ZQpibWlwIDwtIGJtaV90JHAudmFsdWUKCnNtb2tpbmdwIDwtIHNtb2tpbmdfY2hpMiRwLnZhbHVlCmdlbmRlcnAgPC0gZ2VuZGVyX2NoaTIkcC52YWx1ZQpoeXBlcnRlbnNpb25wIDwtIGh5cGVydGVuc2lvbl9jaGkyJHAudmFsdWUKaGRwIDwtIGhkX2NoaTIkcC52YWx1ZQplbXAgPC0gZW1fY2hpMiRwLnZhbHVlCnd0cCA8LSB3dF9jaGkyJHAudmFsdWUKcnRwIDwtIHJ0X2NoaTIkcC52YWx1ZQoKdHRlc3RfdmFsdWVzIDwtIGMoYWdlcCwgZ2x1Y29zcCwgYm1pcCkKdHRlc3RfbGFiZWxzIDwtIGMoImFnZSIsICJnbHVjb3NlIiwgImJtaSIpCgpjaGlzcV92YWx1ZXMgPC0gYyhzbW9raW5ncCwgZ2VuZGVycCwgaHlwZXJ0ZW5zaW9ucCwgaGRwLCBlbXAsIHd0cCwgcnRwKQpjaGlzcV9sYWJlbHMgPC0gYygic21va2luZ19zdGF0dXMiLCAiZ2VuZGVyIiwgImh5cGVydGVuc2lvbiIsICJoZWFydF9kaXNlYXNlIiwgImV2ZXJfbWFycmllZCIsICJ3b3JrX3R5cGUiLCAicmVzaWRlbmNlX3R5cGUiKQoKdGRmIDwtIGRhdGEuZnJhbWUodHRlc3RfbGFiZWxzLCB0dGVzdF92YWx1ZXMpCmNkZiA8LSBkYXRhLmZyYW1lKGNoaXNxX2xhYmVscywgY2hpc3FfdmFsdWVzKQoKCgpgYGAKYGBge3IgZWNobz1GQUxTRSwgcmVzdWx0cz0nYXNpcyd9CnBhbmRvYy5oZWFkZXIoIkNoaS1TcXVhcmUgVGVzdHMiLCAzKQpwYW5kb2MudGFibGUoY2RmKQoKcGFuZG9jLmhlYWRlcigiVC1UZXN0cyIsMykKcGFuZG9jLnRhYmxlKHRkZikKYGBgCgoKCiMjIyMgVGhlIGZlYXR1cmVzIGdlbmRlciBhbmQgcmVzaWRlbmNlIHR5cGUgZG8gbm90IHNob3cgYSBzdGF0aXN0aWNhbGx5IHNpZ25pZmljYW50IGFzc29jaWF0aW9uIHdpdGggdGhlIHN0cm9rZSBvdXRjb21lLiBUaGlzIGNhbiBiZSBvYnNlcnZlZCB2aXN1YWxseSBhcyB3ZWxsIGFzIGJ5IHRoZSBDaGktU3F1YXJlIHRlc3RzLgoKIyMjIFJldmlldyBkaXN0cmlidXRpb24gb2YgbnVtZXJpYyBmZWF0dXJlcwoKYGBge3J9Cmhpc3Qoc3Ryb2tlcyRibWksIG1haW49ImJtaSIsIHhsYWI9ImJtaSIpCmhpc3Qoc3Ryb2tlcyRhdmdfZ2x1Y29zZV9sZXZlbCwgbWFpbj0iYXZlcmFnZSBnbHVjb3NlIGxldmVsIiwgeGxhYj0iYXZnX2dsdWNvc2VfbGV2ZWwiKQpoaXN0KHN0cm9rZXMkYWdlLCBtYWluPSJhZ2UiLCB4bGFiPSJhZ2UiKQoKCmBgYAoKIyMjIyBCb3RoIGF2Z19nbHVjb3NlX2xldmVsIGFuZCBibWkgYXJlIHJpZ2h0LXNrZXdlZC4gVG8gaGVscCBjb3JyZWN0IHRoaXMgYm90aCB2YXJpYWJsZXMgd2lsbCBiZSByZXBsYWNlZCB3aXRoIHRoZWlyIHJlc3BlY3RpdmUgbG9nIHZhbHVlcwpgYGB7cn0Kc3Ryb2tlcyRibWkgPC0gbG9nKHN0cm9rZXMkYm1pKQpzdHJva2VzJGF2Z19nbHVjb3NlX2xldmVsIDwtIGxvZyhzdHJva2VzJGF2Z19nbHVjb3NlX2xldmVsKQoKaGlzdChzdHJva2VzJGJtaSkKaGlzdChzdHJva2VzJGF2Z19nbHVjb3NlX2xldmVsKQpgYGAKIyMjIyBTaW5jZSBuZWl0aGVyIGdlbmRlciBub3IgcmVzaWRlbmNlX3R5cGUgc2hvdyBhbnkgc3RhdGlzdGljYWxseSBzaWduaWZpY2FudCBjb3JyZWxhdGlvbiB3aXRoIHRoZSBzdHJva2Ugb3V0Y29tZSBib3RoIGZlYXR1cmVzIHdpbGwgYmUgcmVtb3ZlZCB0byBhdm9pZCBhbnkgdW5pbnRlbnRpb25hbCBtb2RsZSBjb21wbGV4aXR5IG9yIG5vaXNlCiAKYGBge3J9CnN0cm9rZXMgPC0gc3Ryb2tlc1stYygxLDcpXQpgYGAKIAogCiAKIAoKIyMgUHJlcGFyZSBEYXRhIFNldHMgZm9yIE1vZGVsIFVzYWdlCiogU3BsaXQgaW50byB0cmFpbiwgdmFsaWRhdGlvbiBhbmQgdGVzdCBkYXRhc2V0cyAgIAoqIENyZWF0ZSBiYWxhbmNlZCB0cmFpbmluZyBkYXRhIHNldHMgICAKKiBTZXBhcmF0ZSBpbnRvIGZlYXR1cmVzIGFuZCBsYWJlbHMKKiBTY2FsZSBEYXRhc2V0cyAgCiogT25lLUhvdCBFbmNvZGUgRGF0YXNldHMKYGBge3IgZWNobz1UUlVFLCByZXN1bHRzID0gJ2hpZGUnfQoKIyBjcmVhdGUgdHJhaW4vdGVzdCBkYXRhc2V0IGFzIHdlbGwgYXMgc3Vic2V0IG9mIHRyYWluIGZvciBjcm9zcy12YWxpZGF0aW9uIGFuZCBldmFsdWF0aW9uIHVzYWdlCnNldC5zZWVkKDEyMykKc2VsZWN0aW9uIDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oc3Ryb2tlcyRzdHJva2UsIHA9MC44LCBsaXN0PUZBTFNFKSAgIAp0cmFpbkRhdGFGdWxsID0gc3Ryb2tlc1tzZWxlY3Rpb24sXSAgICAKdGVzdERhdGEgPSBzdHJva2VzWy1zZWxlY3Rpb24sXQpzZXQuc2VlZCgxMjMpCnNlbGVjdGlvbjIgPC0gY3JlYXRlRGF0YVBhcnRpdGlvbih0cmFpbkRhdGFGdWxsJHN0cm9rZSwgcD0wLjgsIGxpc3Q9RkFMU0UpCnRyYWluRGF0YSA9IHRyYWluRGF0YUZ1bGxbc2VsZWN0aW9uMixdCnZhbERhdGEgPSB0cmFpbkRhdGFGdWxsWy1zZWxlY3Rpb24yLF0KCiNjcmVhdGUgYmFsYW5jZWQgdHJhaW5pbmcgZGF0YXNldHMKCnRyYWluRGF0YUJhbGFuY2VkIDwtIFJPU0Uoc3Ryb2tlIH4gLiwgZGF0YT10cmFpbkRhdGEsIHA9MC41KSRkYXRhCnRyYWluRGF0YUZ1bGxCYWxhbmNlZCA8LSBST1NFKHN0cm9rZSB+IC4sIGRhdGE9dHJhaW5EYXRhRnVsbCwgcD0wLjUpJGRhdGEKCiMgc2VwYXJhdGUgZmVhdHVyZXMgYW5kIGxhYmVscwoKdmFsRGF0YUZlYXR1cmVzIDwtIHZhbERhdGFbLTldCnZhbERhdGFMYWJlbHMgPC0gdmFsRGF0YVs5XQoKdGVzdERhdGFGZWF0dXJlcyA8LSB0ZXN0RGF0YVstOV0KdGVzdERhdGFMYWJlbHMgPC0gdGVzdERhdGFbOV0KCnRyYWluRGF0YUZlYXR1cmVzIDwtIHRyYWluRGF0YVstOV0KdHJhaW5EYXRhTGFiZWxzIDwtIHRyYWluRGF0YVs5XQoKdHJhaW5EYXRhRnVsbEZlYXR1cmVzIDwtIHRyYWluRGF0YUZ1bGxbLTldCnRyYWluRGF0YUZ1bGxMYWJlbHMgPC0gdHJhaW5EYXRhRnVsbFs5XQoKdHJhaW5EYXRhQmFsYW5jZWRGZWF0dXJlcyA8LSB0cmFpbkRhdGFCYWxhbmNlZFstOV0KdHJhaW5EYXRhQmFsYW5jZWRMYWJlbHMgPC0gdHJhaW5EYXRhQmFsYW5jZWRbOV0KCnRyYWluRGF0YUZ1bGxCYWxhbmNlZEZlYXR1cmVzIDwtIHRyYWluRGF0YUZ1bGxCYWxhbmNlZFstOV0KdHJhaW5EYXRhRnVsbEJhbGFuY2VkTGFiZWxzIDwtIHRyYWluRGF0YUZ1bGxCYWxhbmNlZFs5XQoKIyBzY2FsZSBmZWF0dXJlcyBkYXRhIHNldHMuIHRlc3REYXRhIHNjYWxlcyB3aXRoIHRyYWluRGF0YUZ1bGwgd2hpbGUgdmFsRGF0YSBzY2FsZXMgd2l0aCB0cmFpbkRhdGEKCnByZVByb2NTY2FsZUZ1bGxUcmFpbiA8LSBwcmVQcm9jZXNzKHRyYWluRGF0YUZ1bGxCYWxhbmNlZEZlYXR1cmVzLCBtZXRob2Q9YygiY2VudGVyIiwgInNjYWxlIikpCnByZVByb2NTY2FsZVRyYWluIDwtIHByZVByb2Nlc3ModHJhaW5EYXRhQmFsYW5jZWRGZWF0dXJlcywgbWV0aG9kPWMoImNlbnRlciIsICJzY2FsZSIpKQoKdHJhaW5EYXRhRnVsbEJhbGFuY2VkRmVhdHVyZXNfcyA8LSBwcmVkaWN0KHByZVByb2NTY2FsZUZ1bGxUcmFpbiwgdHJhaW5EYXRhRnVsbEJhbGFuY2VkRmVhdHVyZXMpCnRlc3REYXRhRmVhdHVyZXNfcyA8LSBwcmVkaWN0KHByZVByb2NTY2FsZUZ1bGxUcmFpbiwgdGVzdERhdGFGZWF0dXJlcykKCnRyYWluRGF0YUJhbGFuY2VkRmVhdHVyZXNfcyA8LSBwcmVkaWN0KHByZVByb2NTY2FsZVRyYWluLCB0cmFpbkRhdGFCYWxhbmNlZEZlYXR1cmVzKQp2YWxEYXRhRmVhdHVyZXNfcyA8LSBwcmVkaWN0KHByZVByb2NTY2FsZVRyYWluLCB2YWxEYXRhRmVhdHVyZXMpCgojIG9uZS1ob3QgZW5jb2RlICBkYXRhc2V0cwpkdW1teTEgPC0gZHVtbXlWYXJzKCJ+IC4iLCBkYXRhPXRyYWluRGF0YUZ1bGxCYWxhbmNlZEZlYXR1cmVzX3MpCnRyYWluRGF0YUZ1bGxCYWxhbmNlZEZlYXR1cmVzX3NlIDwtIGRhdGEuZnJhbWUocHJlZGljdChkdW1teTEsbmV3ZGF0YT10cmFpbkRhdGFGdWxsQmFsYW5jZWRGZWF0dXJlc19zKSkKCmR1bW15MiA8LSBkdW1teVZhcnMoIn4gLiIsIGRhdGE9dHJhaW5EYXRhQmFsYW5jZWRGZWF0dXJlcykKdHJhaW5EYXRhQmFsYW5jZWRGZWF0dXJlc19lIDwtIGRhdGEuZnJhbWUocHJlZGljdChkdW1teTIsbmV3ZGF0YT10cmFpbkRhdGFCYWxhbmNlZEZlYXR1cmVzKSkKCmR1bW15MyA8LSBkdW1teVZhcnMoIn4gLiIsIGRhdGE9dHJhaW5EYXRhRmVhdHVyZXMpCnRyYWluRGF0YUZlYXR1cmVzX2UgPC0gZGF0YS5mcmFtZShwcmVkaWN0KGR1bW15MyxuZXdkYXRhPXRyYWluRGF0YUZlYXR1cmVzKSkKCmR1bW15NCA8LSBkdW1teVZhcnMoIn4gLiIsIGRhdGE9dHJhaW5EYXRhRnVsbEZlYXR1cmVzKQp0cmFpbkRhdGFGdWxsRmVhdHVyZXNfZSA8LSBkYXRhLmZyYW1lKHByZWRpY3QoZHVtbXk0LG5ld2RhdGE9dHJhaW5EYXRhRnVsbEZlYXR1cmVzKSkKCgp0ZXN0RGF0YUZlYXR1cmVzX3NlIDwtIGRhdGEuZnJhbWUocHJlZGljdChkdW1teTEsbmV3ZGF0YT10ZXN0RGF0YUZlYXR1cmVzX3MpKQoKCnZhbERhdGFGZWF0dXJlc19lIDwtIGRhdGEuZnJhbWUocHJlZGljdChkdW1teTIsbmV3ZGF0YT12YWxEYXRhRmVhdHVyZXMpKQoKZnVsbHNpemUgPC0gZGltKHRyYWluRGF0YUZ1bGwpCnRyYWluc2l6ZSA8LSBkaW0odHJhaW5EYXRhKQp2YWxzaXplIDwtIGRpbSh2YWxEYXRhKQp0ZXN0c2l6ZSA8LSBkaW0odGVzdERhdGEpCgpkaXN0ZnVsbCA8LSB0YWJsZSh0cmFpbkRhdGFGdWxsJHN0cm9rZSkKZGlzdHRyYWluIDwtIHRhYmxlKHRyYWluRGF0YSRzdHJva2UpCmRpc3R2YWwgPC0gdGFibGUodmFsRGF0YSRzdHJva2UpCmRpc3R0ZXN0IDwtIHRhYmxlKHRlc3REYXRhJHN0cm9rZSkKCgpgYGAKIyMjIFZlcmlmeSBkaW1lbnNpb25zIGFuZCBkaXN0cmlidXRpb24gb2YgcmVzcG9uc2UgdmFyaWFibGUgZm9yIGRhdGFzZXRzICAgCgpgYGB7ciBlY2hvPUZBTFNFLCByZXN1bHRzPSdhc2lzJ30KcGFuZG9jLmhlYWRlcigidHJhaW5EYXRhRnVsbCIsIDQpCnBhbmRvYy50YWJsZShmdWxsc2l6ZSkKCnBhbmRvYy5oZWFkZXIoInRyYWluRGF0YSIsIDQpCnBhbmRvYy50YWJsZSh0cmFpbnNpemUpCgpwYW5kb2MuaGVhZGVyKCJ2YWxEYXRhIiwgNCkKcGFuZG9jLnRhYmxlKHZhbHNpemUpCgpwYW5kb2MuaGVhZGVyKCJ0ZXN0RGF0YSIsNCkKcGFuZG9jLnRhYmxlKHRlc3RzaXplKQoKcGFuZG9jLmhlYWRlcigidHJhaW5EYXRhRnVsbCIsIDQpCnBhbmRvYy50YWJsZShwcm9wLnRhYmxlKGRpc3RmdWxsKSkKCnBhbmRvYy5oZWFkZXIoInRyYWluRGF0YSIsIDQpCnBhbmRvYy50YWJsZShwcm9wLnRhYmxlKGRpc3R0cmFpbikpCgpwYW5kb2MuaGVhZGVyKCJ2YWxEYXRhIiwgNCkKcGFuZG9jLnRhYmxlKHByb3AudGFibGUoZGlzdHZhbCkpCgpwYW5kb2MuaGVhZGVyKCJ0ZXN0RGF0YSIsNCkKcGFuZG9jLnRhYmxlKHByb3AudGFibGUoZGlzdHRlc3QpKQoKCgpgYGAKCiMjIyMgVGhlIHByb3BvcnRpb24gb2YgIm5vIiB0byAieWVzIiB2YWx1ZXMgaW4gYWxsIGRhdGFzZXRzIGlzIGVxdWl2YWxlbnQuCgoKCgoKCgojIEZpdCBNb2RlbHMKIyMgRGVmaW5lIGNvbnRyb2wgZm9yIG1vZGVscyB0cmFpbmVkIHdpdGggQ2FyZXQKYGBge3J9Cm15Q29udHJvbCA8LSB0cmFpbkNvbnRyb2wobWV0aG9kPSdyZXBlYXRlZGN2JywKICAgICAgICAgICAgICAgICAgICAgICAgICBudW1iZXI9MTAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgcmVwZWF0cz01LAogICAgICAgICAgICAgICAgICAgICAgICAgIHZlcmJvc2VJdGVyID0gRkFMU0UsCiAgICAgICAgICAgICAgICAgICAgICAgICAgY2xhc3NQcm9icz1UUlVFLAogICAgICAgICAgICAgICAgICAgICAgICAgIHN1bW1hcnlGdW5jdGlvbj10d29DbGFzc1N1bW1hcnksCiAgICAgICAgICAgICAgICAgICAgICAgICAgc2VsZWN0aW9uRnVuY3Rpb249Im9uZVNFIiwKICAgICAgICAgICAgICAgICAgICAgICAgICBzYW1wbGluZz0ncm9zZScpCgpteUNvbnRyb2wyIDwtIHRyYWluQ29udHJvbChtZXRob2Q9J3JlcGVhdGVkY3YnLAogICAgICAgICAgICAgICAgICAgICAgICAgIG51bWJlcj0xMCwKICAgICAgICAgICAgICAgICAgICAgICAgICByZXBlYXRzPTUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgdmVyYm9zZUl0ZXIgPSBGQUxTRSwKICAgICAgICAgICAgICAgICAgICAgICAgICBjbGFzc1Byb2JzPVRSVUUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgc3VtbWFyeUZ1bmN0aW9uPXR3b0NsYXNzU3VtbWFyeSwKICAgICAgICAgICAgICAgICAgICAgICAgICBzZWxlY3Rpb25GdW5jdGlvbj0ib25lU0UiKQpgYGAKCgoKIyMgS05OIE1vZGVsIFRyYWluaW5nIGFuZCBFdmFsdWF0aW9uCkVuc3VyaW5nIHRoZSBzcXVhcmUgcm9vdCBvZiB0aGUgbnVtYmVyIG9mIG9ic2VydmF0aW9ucyBpcyBjb250YWluZWQgaW4gdGhlIHZhbHVlcyBvZiBrIGJlaW5nIGV4cGxvcmVkCmBgYHtyIHdhcm5pbmc9RkFMU0V9CnNldC5zZWVkKDEyMykKCmtubl9ncmlkIDwtIGV4cGFuZC5ncmlkKGs9c2VxKDEsMTAwLGJ5PTIpKQoKa25uX3RyYWluIDwtIGNiaW5kKHRyYWluRGF0YUZlYXR1cmVzX2UsIHRyYWluRGF0YUxhYmVscykKa25uX21vZGVsIDwtIGNhcmV0Ojp0cmFpbihzdHJva2UgfiAuLCBkYXRhPWtubl90cmFpbiwgbWV0aG9kPSdrbm4nLCB0ckNvbnRyb2w9bXlDb250cm9sLCB0dW5lR3JpZCA9IGtubl9ncmlkLCBwcmVQcm9jID0gJ3JhbmdlJywgbWV0cmljPSdST0MnKQoKCgpgYGAKCgoKCmBgYHtyfQoKCnBsb3Qoa25uX21vZGVsKQoKa25uX3ByZWQgPC0gcHJlZGljdChrbm5fbW9kZWwsIG5ld2RhdGE9dmFsRGF0YUZlYXR1cmVzX2UpCmtubl9wcm9iIDwtIHByZWRpY3Qoa25uX21vZGVsLCBuZXdkYXRhPXZhbERhdGFGZWF0dXJlc19lLCB0eXBlPSdwcm9iJykKa25uX3Jlc3VsdHMgPC0gY29uZnVzaW9uTWF0cml4KGtubl9wcmVkLCB2YWxEYXRhTGFiZWxzJHN0cm9rZSwgcG9zaXRpdmUgPSAiWWVzIikKCmtubl9yZXN1bHRzMiA8LSBkYXRhLmZyYW1lKGFzLmZhY3Rvcihrbm5fcHJlZCksIGFzLmZhY3Rvcih2YWxEYXRhTGFiZWxzJHN0cm9rZSksIGtubl9wcm9iKQpjb2xuYW1lcyhrbm5fcmVzdWx0czIpIDwtIGMoInByZWQiLCAib2JzIiwgIk5vIiwgIlllcyIpCmtubl9tb2RlbAprbm5fcmVzdWx0cwprbm5fMmNsYXNzIDwtIHR3b0NsYXNzU3VtbWFyeShrbm5fcmVzdWx0czIsIGxldiA9IGMoIk5vIiwgIlllcyIpKQprbm5fMmNsYXNzWzFdCmtubl9yb2MgPC0gcm9jKHZhbERhdGFMYWJlbHMkc3Ryb2tlIH4ga25uX3Byb2IkWWVzLCBwbG90PVRSVUUsIHByaW50LmF1Yz1UUlVFLCBjb2w9J2JsYWNrJywgbHdkPTQsIGxlZ2FjeS5heGVzPVRSVUUsIG1haW49IlJPQyBDdXJ2ZSIsIGFkZD1GQUxTRSkKYGBgCgoKCgojIyBMb2dpc3RpYyBSZWdyZXNzaW9uIE1vZGVsIHdpdGggRWxhc3RpYyBOZXQgUmVndWxhcml6YXRpb24KYGBge3J9CnNldC5zZWVkKDEyMykKCmdsbV9ncmlkIDwtIGV4cGFuZC5ncmlkKGFscGhhPXNlcSgwLDEsIGxlbmd0aD0xMCksIGxhbWJkYSA9IDEwXnNlcSgtMywzLCBsZW5ndGg9MTAwKSkKCmdsbV90cmFpbiA8LSBjYmluZCh0cmFpbkRhdGFGZWF0dXJlc19lLCB0cmFpbkRhdGFMYWJlbHMpCmdsbV9tb2RlbCA8LSBjYXJldDo6dHJhaW4oc3Ryb2tlIH4gLiwgZGF0YT1nbG1fdHJhaW4sIG1ldGhvZD0nZ2xtbmV0JywgdHJDb250cm9sPW15Q29udHJvbCwgdHVuZUdyaWQgPSBnbG1fZ3JpZCwgcHJlUHJvYyA9ICJyYW5nZSIsIG1ldHJpYz0nUk9DJywgZmFtaWx5PSdiaW5vbWlhbCcpCmBgYAoKYGBgIHtSfQoKCmdsbV9wcmVkIDwtIHByZWRpY3QoZ2xtX21vZGVsLCBuZXdkYXRhID0gdmFsRGF0YUZlYXR1cmVzX2UpCmdsbV9wcm9iIDwtIHByZWRpY3QoZ2xtX21vZGVsLCBuZXdkYXRhID0gdmFsRGF0YUZlYXR1cmVzX2UsIHR5cGU9J3Byb2InKQpnbG1fcmVzdWx0cyA8LSBjb25mdXNpb25NYXRyaXgoZ2xtX3ByZWQsIHZhbERhdGFMYWJlbHMkc3Ryb2tlLCBwb3NpdGl2ZT0iWWVzIikKCmdsbV9yZXN1bHRzCgoKZ2xtX3Jlc3VsdHMyIDwtIGRhdGEuZnJhbWUoYXMuZmFjdG9yKGdsbV9wcmVkKSwgYXMuZmFjdG9yKHZhbERhdGFMYWJlbHMkc3Ryb2tlKSwgZ2xtX3Byb2IpCgoKY29sbmFtZXMoZ2xtX3Jlc3VsdHMyKSA8LSBjKCJwcmVkIiwgIm9icyIsICJObyIsICJZZXMiKQoKCgpnbG1fMmNsYXNzIDwtIHR3b0NsYXNzU3VtbWFyeShnbG1fcmVzdWx0czIsIGxldj1jKCJObyIsICJZZXMiKSkKZ2xtXzJjbGFzc1sxXQpnbG1fcm9jIDwtIHJvYyh2YWxEYXRhTGFiZWxzJHN0cm9rZSB+IGdsbV9wcm9iJFllcywgcGxvdD1UUlVFLCBwcmludC5hdWM9VFJVRSwgY29sPSdibGFjaycsIGx3ZD00LCBsZWdhY3kuYXhlcz1UUlVFLCBtYWluPSJST0MgQ3VydmUiLCBhZGQ9RkFMU0UpCmNvZWYoZ2xtX21vZGVsJGZpbmFsTW9kZWwsZ2xtX21vZGVsJGJlc3RUdW5lJGxhbWJkYSkKdmFySW1wKGdsbV9tb2RlbCkKYGBgCgoKCiMjIERlY2lzaW9uIFRyZWUgTW9kZWwKCmBgYHtyfQoKCnNldC5zZWVkKDEyMykKCnRyZWVfZ3JpZCA8LSBleHBhbmQuZ3JpZChjcD1zZXEoMC4wLDEuMCxieT0uMDAxKSkKCgoKdHJlZV9kYXRhIDwtIGNiaW5kKHRyYWluRGF0YUZlYXR1cmVzLCB0cmFpbkRhdGFMYWJlbHMpCnRyZWVfbW9kZWwgPC0gY2FyZXQ6OnRyYWluKHN0cm9rZSB+LixkYXRhPXRyZWVfZGF0YSwgbWV0aG9kPSdycGFydCcsIHRyQ29udHJvbD1teUNvbnRyb2wsIHByZVByb2MgPSAicmFuZ2UiLCBtZXRyaWM9J1JPQycsIHR1bmVHcmlkPXRyZWVfZ3JpZCkKCmBgYAoKYGBge3J9CgoKCgp0cmVlX3ByZWQgPC0gcHJlZGljdCh0cmVlX21vZGVsLCB2YWxEYXRhRmVhdHVyZXMpCnRyZWVfcHJvYiA8LSBwcmVkaWN0KHRyZWVfbW9kZWwsIG5ld2RhdGEgPSB2YWxEYXRhRmVhdHVyZXMsIHR5cGU9J3Byb2InKQp0cmVlX3Jlc3VsdHMgPC0gY29uZnVzaW9uTWF0cml4KHRyZWVfcHJlZCwgdmFsRGF0YUxhYmVscyRzdHJva2UsIHBvc2l0aXZlPSJZZXMiKQp0cmVlX3Jlc3VsdHMKCgp0cmVlX3Jlc3VsdHMyIDwtIGRhdGEuZnJhbWUoYXMuZmFjdG9yKHRyZWVfcHJlZCksIGFzLmZhY3Rvcih2YWxEYXRhTGFiZWxzJHN0cm9rZSksIHRyZWVfcHJvYikKCgpjb2xuYW1lcyh0cmVlX3Jlc3VsdHMyKSA8LSBjKCJwcmVkIiwgIm9icyIsICJObyIsICJZZXMiKQoKCgp0cmVlXzJjbGFzcyA8LSB0d29DbGFzc1N1bW1hcnkodHJlZV9yZXN1bHRzMiwgbGV2PWMoIk5vIiwgIlllcyIpKQp0cmVlXzJjbGFzc1sxXQp0cmVlX3JvYyA8LSByb2ModmFsRGF0YUxhYmVscyRzdHJva2UgfiB0cmVlX3Byb2IkWWVzLCBwbG90PVRSVUUsIHByaW50LmF1Yz1UUlVFLCBjb2w9J2JsYWNrJywgbHdkPTQsIGxlZ2FjeS5heGVzPVRSVUUsIG1haW49IlJPQyBDdXJ2ZSIsIGFkZD1GQUxTRSkKZmFuY3lScGFydFBsb3QodHJlZV9tb2RlbCRmaW5hbE1vZGVsKQpgYGAKQ29uc2lkZXJpbmcgdGhlIHBvb3IgcGVyZm9ybWFuY2Ugb2YgdGhlIGRlY2lzaW9uIHRyZWUsIGEgc2Vjb25kIG1vZGVsIHdpbGwgYmUgYnVpbHQgYnV0IHdpbGwgbm90IGJhbGFuY2UgdGhlIGRhdGFzZXQgcHJpb3IgdG8gdXNlCgoKYGBge3J9CgoKc2V0LnNlZWQoMTIzKQoKdHJlZV9ncmlkIDwtIGV4cGFuZC5ncmlkKGNwPXNlcSgwLjAsMS4wLGJ5PS4wMDEpKQoKCgp0cmVlX2RhdGEgPC0gY2JpbmQodHJhaW5EYXRhRmVhdHVyZXMsIHRyYWluRGF0YUxhYmVscykKdHJlZV9tb2RlbCA8LSBjYXJldDo6dHJhaW4oc3Ryb2tlIH4uLGRhdGE9dHJlZV9kYXRhLCBtZXRob2Q9J3JwYXJ0JywgdHJDb250cm9sPW15Q29udHJvbDIsIHByZVByb2MgPSAicmFuZ2UiLCBtZXRyaWM9J1JPQycsIHR1bmVHcmlkPXRyZWVfZ3JpZCkKCmBgYAoKYGBge3J9CgoKCgp0cmVlX3ByZWQgPC0gcHJlZGljdCh0cmVlX21vZGVsLCB2YWxEYXRhRmVhdHVyZXMpCnRyZWVfcHJvYiA8LSBwcmVkaWN0KHRyZWVfbW9kZWwsIG5ld2RhdGEgPSB2YWxEYXRhRmVhdHVyZXMsIHR5cGU9J3Byb2InKQp0cmVlX3Jlc3VsdHMgPC0gY29uZnVzaW9uTWF0cml4KHRyZWVfcHJlZCwgdmFsRGF0YUxhYmVscyRzdHJva2UsIHBvc2l0aXZlPSJZZXMiKQp0cmVlX3Jlc3VsdHMKCgp0cmVlX3Jlc3VsdHMyIDwtIGRhdGEuZnJhbWUoYXMuZmFjdG9yKHRyZWVfcHJlZCksIGFzLmZhY3Rvcih2YWxEYXRhTGFiZWxzJHN0cm9rZSksIHRyZWVfcHJvYikKCgpjb2xuYW1lcyh0cmVlX3Jlc3VsdHMyKSA8LSBjKCJwcmVkIiwgIm9icyIsICJObyIsICJZZXMiKQoKCgp0cmVlXzJjbGFzcyA8LSB0d29DbGFzc1N1bW1hcnkodHJlZV9yZXN1bHRzMiwgbGV2PWMoIk5vIiwgIlllcyIpKQp0cmVlXzJjbGFzc1sxXQp0cmVlX3JvYyA8LSByb2ModmFsRGF0YUxhYmVscyRzdHJva2UgfiB0cmVlX3Byb2IkWWVzLCBwbG90PVRSVUUsIHByaW50LmF1Yz1UUlVFLCBjb2w9J2JsYWNrJywgbHdkPTQsIGxlZ2FjeS5heGVzPVRSVUUsIG1haW49IlJPQyBDdXJ2ZSIsIGFkZD1GQUxTRSkKZmFuY3lScGFydFBsb3QodHJlZV9tb2RlbCRmaW5hbE1vZGVsKQpgYGAKCiMjIFJhbmRvbSBGb3Jlc3QgTW9kZWwKCmBgYHtyfQoKcmZfZ3JpZCA8LSBleHBhbmQuZ3JpZChtdHJ5PWMoMSwyLDQsNiw4KSkKc2V0LnNlZWQoMTIzKQoKcmZfZGF0YSA8LSBjYmluZCh0cmFpbkRhdGFGZWF0dXJlcywgdHJhaW5EYXRhTGFiZWxzKQpyZl9tb2RlbCA8LSBjYXJldDo6dHJhaW4oc3Ryb2tlIH4uLGRhdGE9cmZfZGF0YSwgbWV0aG9kPSdyZicsIHRyQ29udHJvbD1teUNvbnRyb2wsIHByZVByb2MgPSAicmFuZ2UiLCBtZXRyaWM9J1JPQycsIHR1bmVHcmlkPXJmX2dyaWQpCgpyZl9wcmVkIDwtIHByZWRpY3QocmZfbW9kZWwsIHZhbERhdGFGZWF0dXJlcykKcmZfcHJvYiA8LSBwcmVkaWN0KHJmX21vZGVsLCB2YWxEYXRhRmVhdHVyZXMsIHR5cGU9J3Byb2InKQpgYGAKCmBgYCB7cn0KcmZfcmVzdWx0cyA8LSBjb25mdXNpb25NYXRyaXgocmZfcHJlZCx2YWxEYXRhTGFiZWxzJHN0cm9rZSwgcG9zaXRpdmU9IlllcyIpCnJmX3Jlc3VsdHMKcmZfcmVzdWx0czIgPC0gZGF0YS5mcmFtZShhcy5mYWN0b3IocmZfcHJlZCksIGFzLmZhY3Rvcih2YWxEYXRhTGFiZWxzJHN0cm9rZSksIHJmX3Byb2IpCmNvbG5hbWVzKHJmX3Jlc3VsdHMyKSA8LSBjKCJwcmVkIiwgIm9icyIsICJObyIsICJZZXMiKQoKCnJmXzJjbGFzcyA8LSB0d29DbGFzc1N1bW1hcnkocmZfcmVzdWx0czIsIGxldj1jKCJObyIsICJZZXMiKSkKcmZfMmNsYXNzWzFdCnJmX3JvYyA8LSByb2ModmFsRGF0YUxhYmVscyRzdHJva2UgfiByZl9wcm9iJFllcywgcGxvdD1UUlVFLCBwcmludC5hdWM9VFJVRSwgY29sPSdibGFjaycsIGx3ZD00LCBsZWdhY3kuYXhlcz1UUlVFLCBtYWluPSJST0MgQ3VydmUiKQp2YXJJbXAocmZfbW9kZWwsIHNjYWxlID0gVFJVRSkKcmZfbW9kZWwKcGxvdChyZl9tb2RlbCkKYGBgCgoKCgoKCiMjIExpbmVhciBTVk0gTW9kZWwKYGBge3IgZWNobz1UUlVFfQpzZXQuc2VlZCgxMjMpCmxzdm1fZ3JpZCA8LSBleHBhbmQuZ3JpZChDID0gMyoqKC03OjcpKQpsc3ZtX2RhdGEgPC0gY2JpbmQodHJhaW5EYXRhRmVhdHVyZXNfZSwgdHJhaW5EYXRhTGFiZWxzKQpsc3ZtX21vZGVsIDwtIGNhcmV0Ojp0cmFpbihzdHJva2Ugfi4sIGRhdGEgPSBsc3ZtX2RhdGEsIG1ldGhvZCA9ICJzdm1MaW5lYXIiLCB0ckNvbnRyb2wgPSBteUNvbnRyb2wsIHByZVByb2Nlc3MgPSAncmFuZ2UnLCB0dW5lR3JpZCA9IGxzdm1fZ3JpZCwgbWV0cmljPSAnUk9DJykKYGBgCgpgYGB7cn0KbHN2bV9wcmVkIDwtIHByZWRpY3QobHN2bV9tb2RlbCwgdmFsRGF0YUZlYXR1cmVzX2UpCmxzdm1fcHJvYiA8LSBwcmVkaWN0KGxzdm1fbW9kZWwsIHZhbERhdGFGZWF0dXJlc19lLCB0eXBlPSdwcm9iJykKbHN2bV9yZXN1bHRzIDwtIGNvbmZ1c2lvbk1hdHJpeChsc3ZtX3ByZWQsIHZhbERhdGFMYWJlbHMkc3Ryb2tlLCBwb3NpdGl2ZT0iWWVzIikKbHN2bV9yZXN1bHRzMiA8LSBkYXRhLmZyYW1lKGFzLmZhY3Rvcihsc3ZtX3ByZWQpLCBhcy5mYWN0b3IodmFsRGF0YUxhYmVscyRzdHJva2UpLCBsc3ZtX3Byb2IpCmxzdm1fcmVzdWx0cwoKY29sbmFtZXMobHN2bV9yZXN1bHRzMikgPC0gYygicHJlZCIsICJvYnMiLCAiTm8iLCAiWWVzIikKbHN2bV8yY2xhc3MgPC0gdHdvQ2xhc3NTdW1tYXJ5KGxzdm1fcmVzdWx0czIsIGxldj1jKCJObyIsICJZZXMiKSkKbHN2bV8yY2xhc3NbMV0KdmFySW1wKGxzdm1fbW9kZWwsIHNjYWxlID0gVFJVRSkKbHN2bV9yb2MgPC0gcm9jKHZhbERhdGFMYWJlbHMkc3Ryb2tlIH4gbHN2bV9wcm9iJFllcywgcGxvdD1UUlVFLCBwcmludC5hdWM9VFJVRSwgY29sPSdibGFjaycsIGx3ZD00LCBsZWdhY3kuYXhlcz1UUlVFLCBtYWluPSJST0MgQ3VydmUiLCBhZGQ9RkFMU0UpCgpwbG90KGxzdm1fbW9kZWwsIHNjYWxlcz1saXN0KHg9bGlzdChsb2c9MykpKQpsc3ZtX21vZGVsCgpgYGAKCiMgR3JhZGllbnQgQm9vc3QgTW9kZWwKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGluY2x1ZGU9VFJVRX0KZ2JtX0dyaWQgPC0gIGV4cGFuZC5ncmlkKGludGVyYWN0aW9uLmRlcHRoID0gYygxLCAzLCA2LCA5LCAxMCksCiAgICAgICAgICAgICAgICAgICAgbi50cmVlcyA9IHNlcSgyNTAsMjAwMCwyNTApLCAKICAgICAgICAgICAgICAgICAgICBzaHJpbmthZ2UgPSBzZXEoLjAwMDUsIC4wNSwuMDAwNSksCiAgICAgICAgICAgICAgICAgICAgbi5taW5vYnNpbm5vZGUgPSAxMCkKCnNldC5zZWVkKDEyMykKZ2JtX2RhdGEgPSBjYmluZCh0cmFpbkRhdGFGZWF0dXJlc19lLCB0cmFpbkRhdGFMYWJlbHMpCmdibV9tb2RlbCA8LSBjYXJldDo6dHJhaW4oc3Ryb2tlIH4uLCBkYXRhID0gZ2JtX2RhdGEsIG1ldGhvZCA9ICJnYm0iLCB0ckNvbnRyb2wgPSBteUNvbnRyb2wsIHByZVByb2Nlc3MgPSAncmFuZ2UnLCBtZXRyaWM9ICdST0MnLCB0dW5lTGVuZ3RoID0gMTApCmBgYAoKYGBge3J9CmdibV9wcmVkIDwtIHByZWRpY3QoZ2JtX21vZGVsLCB2YWxEYXRhRmVhdHVyZXNfZSkKZ2JtX3Byb2IgPC0gcHJlZGljdChnYm1fbW9kZWwsIHZhbERhdGFGZWF0dXJlc19lLCB0eXBlPSdwcm9iJykKCmdibV9yZXN1bHRzIDwtIGNvbmZ1c2lvbk1hdHJpeChnYm1fcHJlZCwgdmFsRGF0YUxhYmVscyRzdHJva2UsIHBvc2l0aXZlPSdZZXMnKQoKZ2JtX3Jlc3VsdHMKCmdibV9yZXN1bHRzMiA8LSBkYXRhLmZyYW1lKGFzLmZhY3RvcihnYm1fcHJlZCksIGFzLmZhY3Rvcih2YWxEYXRhTGFiZWxzJHN0cm9rZSksIGdibV9wcm9iKQoKCmNvbG5hbWVzKGdibV9yZXN1bHRzMikgPC0gYygicHJlZCIsICJvYnMiLCAiTm8iLCAiWWVzIikKCgoKZ2JtXzJjbGFzcyA8LSB0d29DbGFzc1N1bW1hcnkoZ2JtX3Jlc3VsdHMyLCBsZXY9YygiTm8iLCAiWWVzIikpCmdibV8yY2xhc3NbMV0KZ2JtX3JvYyA8LSByb2ModmFsRGF0YUxhYmVscyRzdHJva2UgfiBnYm1fcHJvYiRZZXMsIHBsb3Q9VFJVRSwgcHJpbnQuYXVjPVRSVUUsIGNvbD0nYmxhY2snLCBsd2Q9NCwgbGVnYWN5LmF4ZXM9VFJVRSwgbWFpbj0iUk9DIEN1cnZlIiwgYWRkPUZBTFNFKQoKZ2JtX21vZGVsCnBsb3QoZ2JtX21vZGVsKQoKYGBgCgoKCiMgTmV1cmFsIE5ldHdvcmsgTW9kZWwKIyMgU2NhbGUgZGF0YXNldHMgZm9yIHVzYWdlCmBgYHtyfQoKbnVtZXJpY2FsIDwtIGMoMSw2LDcpCgoKdHJhaW5EYXRhRnVsbE51bSA8LSB0cmFpbkRhdGFGdWxsW251bWVyaWNhbF0KdHJhaW5EYXRhRnVsbENhdCA8LSB0cmFpbkRhdGFGdWxsWy1udW1lcmljYWxdCgoKCnRyYWluRGF0YU51bSA8LSB0cmFpbkRhdGFbbnVtZXJpY2FsXQp0cmFpbkRhdGFDYXQgPC0gdHJhaW5EYXRhWy1udW1lcmljYWxdCgoKdmFsRGF0YU51bSA8LSB2YWxEYXRhW251bWVyaWNhbF0KdmFsRGF0YUNhdCA8LSB2YWxEYXRhWy1udW1lcmljYWxdCgoKdGVzdERhdGFOdW0gPC0gdGVzdERhdGFbbnVtZXJpY2FsXQp0ZXN0RGF0YUNhdCA8LSB0ZXN0RGF0YVstbnVtZXJpY2FsXQoKdHJhaW5EYXRhRnVsbFNjYWxlZCA8LSBzY2FsZSh0cmFpbkRhdGFGdWxsTnVtKQpjb2xfbWVhbnNfdHJhaW4gPC0gYXR0cih0cmFpbkRhdGFGdWxsU2NhbGVkLCAic2NhbGVkOmNlbnRlciIpCmNvbF9zdGRkZXZzX3RyYWluIDwtIGF0dHIodHJhaW5EYXRhRnVsbFNjYWxlZCwgInNjYWxlZDpzY2FsZSIpCgp0ZXN0RGF0YVNjYWxlZCA8LSBzY2FsZSh0ZXN0RGF0YU51bSwgY2VudGVyID0gY29sX21lYW5zX3RyYWluLCBzY2FsZSA9IGNvbF9zdGRkZXZzX3RyYWluKSAgCgp0cmFpbkRhdGFTY2FsZWQgPC0gc2NhbGUodHJhaW5EYXRhTnVtKQpjb2xfbWVhbnNfdHJhaW4gPC0gYXR0cih0cmFpbkRhdGFTY2FsZWQsICJzY2FsZWQ6Y2VudGVyIikKY29sX3N0ZGRldnNfdHJhaW4gPC0gYXR0cih0cmFpbkRhdGFTY2FsZWQsICJzY2FsZWQ6c2NhbGUiKQoKdmFsRGF0YVNjYWxlZCA8LSBzY2FsZSh2YWxEYXRhTnVtLCBjZW50ZXIgPSBjb2xfbWVhbnNfdHJhaW4sIHNjYWxlID0gY29sX3N0ZGRldnNfdHJhaW4pICAKCm5uRnVsbFRyYWluRGF0YSA8LSBjYmluZCh0cmFpbkRhdGFGdWxsU2NhbGVkLCB0cmFpbkRhdGFGdWxsQ2F0KQpublRyYWluRGF0YSA8LSBjYmluZCh0cmFpbkRhdGFTY2FsZWQsIHRyYWluRGF0YUNhdCkKbm5UZXN0RGF0YSA8LSBjYmluZCh0ZXN0RGF0YVNjYWxlZCwgdGVzdERhdGFDYXQpCm5uVmFsRGF0YSA8LSBjYmluZCh2YWxEYXRhU2NhbGVkLCB2YWxEYXRhQ2F0KQoKCiMgYmFsYW5jZSB0cmFpbmluZyBkYXRhCgpublRyYWluRGF0YSA8LSBST1NFKHN0cm9rZSB+IC4sIGRhdGE9bm5UcmFpbkRhdGEsIHA9MC41KSRkYXRhCgpubkZ1bGxUcmFpbkRhdGEgPC0gUk9TRShzdHJva2UgfiAuLCBkYXRhPW5uRnVsbFRyYWluRGF0YSwgcD0wLjUpJGRhdGEKCm5uVmFsQmFsIDwtIFJPU0Uoc3Ryb2tlIH4gLiwgZGF0YT1ublZhbERhdGEsIHA9MC41KSRkYXRhCgpgYGAKCiMjIHNlcGFyYXRlIGZlYXR1cmVzIGZyb20gbGFiZWxzCgpgYGB7cn0Kbm5GdWxsVHJhaW5GZWF0dXJlcyA8LSBubkZ1bGxUcmFpbkRhdGFbLTldCm5uRnVsbFRyYWluTGFiZWxzIDwtIG5uRnVsbFRyYWluRGF0YVs5XQoKbm5UcmFpbkZlYXR1cmVzIDwtIG5uVHJhaW5EYXRhWy05XQpublRyYWluTGFiZWxzIDwtIG5uVHJhaW5EYXRhWzldCgpublRlc3RGZWF0dXJlcyA8LSBublRlc3REYXRhWy05XQpublRlc3RMYWJlbHMgPC0gbm5UZXN0RGF0YVs5XQoKbm5WYWxGZWF0dXJlcyA8LSBublZhbERhdGFbLTldCm5uVmFsTGFiZWxzIDwtIG5uVmFsRGF0YVs5XQoKbm5WYWxCYWxGZWF0dXJlcyA8LSBublZhbEJhbFstOV0Kbm5WYWxCYWxMYWJlbHMgPC0gbm5WYWxCYWxbOV0KCm5uVmFsTGFiZWxzJHN0cm9rZSA8LSBpZmVsc2Uobm5WYWxMYWJlbHMkc3Ryb2tlID09ICdObycsIDAsIDEpCm5uVGVzdExhYmVscyRzdHJva2UgPC0gaWZlbHNlKG5uVGVzdExhYmVscyRzdHJva2UgPT0gJ05vJywgMCwgMSkKbm5UcmFpbkxhYmVscyRzdHJva2UgPC0gaWZlbHNlKG5uVHJhaW5MYWJlbHMkc3Ryb2tlID09ICdObycsIDAsIDEpCm5uRnVsbFRyYWluTGFiZWxzJHN0cm9rZSA8LSBpZmVsc2Uobm5GdWxsVHJhaW5MYWJlbHMkc3Ryb2tlID09ICdObycsIDAsIDEpCm5uVmFsQmFsTGFiZWxzJHN0cm9rZSA8LSBpZmVsc2Uobm5WYWxCYWxMYWJlbHMkc3Ryb2tlID09ICdObycsIDAsIDEpCgoKYGBgCgpgYGB7ciBlY2hvPUZBTFNFfQpwYW5kb2MuaGVhZGVyKCJTdHJva2UgRGlzdHJ1YnV0aW9uIGZvciBUcmFpbiBEYXRhc2V0IiwzKQpwYW5kb2MudGFibGUodGFibGUobm5UcmFpbkxhYmVscyRzdHJva2UpKQoKcGFuZG9jLmhlYWRlcigiU3Ryb2tlIERpc3RydWJ1dGlvbiBmb3IgZnVsbFRyYWluIERhdGFzZXQiLDMpCnBhbmRvYy50YWJsZSh0YWJsZShubkZ1bGxUcmFpbkxhYmVscyRzdHJva2UpKQpgYGAKCgojIyBvbmUtaG90IGVuY29kZSBjYXRlZ29yaWNhbCBmZWF0dXJlcwoKYGBge3J9CgpkdW1teUZ1bGxUcmFpbiA8LSBkdW1teVZhcnMoIn4gLiIsIGRhdGE9bm5GdWxsVHJhaW5GZWF0dXJlcykgCmR1bW15VHJhaW4gPC0gZHVtbXlWYXJzKCJ+IC4iLCBkYXRhPW5uVHJhaW5GZWF0dXJlcykKCm5uRnVsbFRyYWluRmVhdHVyZXMgPC0gZGF0YS5mcmFtZShwcmVkaWN0KGR1bW15RnVsbFRyYWluLG5ld2RhdGE9bm5GdWxsVHJhaW5GZWF0dXJlcykpCm5uVGVzdEZlYXR1cmVzIDwtIGRhdGEuZnJhbWUocHJlZGljdChkdW1teUZ1bGxUcmFpbixuZXdkYXRhPW5uVGVzdEZlYXR1cmVzKSkKCm5uVHJhaW5GZWF0dXJlcyA8LSBkYXRhLmZyYW1lKHByZWRpY3QoZHVtbXlUcmFpbixuZXdkYXRhPW5uVHJhaW5GZWF0dXJlcykpCm5uVmFsRmVhdHVyZXMgPC0gZGF0YS5mcmFtZShwcmVkaWN0KGR1bW15VHJhaW4sbmV3ZGF0YT1ublZhbEZlYXR1cmVzKSkKbm5WYWxCYWxGZWF0dXJlcyA8LSBkYXRhLmZyYW1lKHByZWRpY3QoZHVtbXlUcmFpbixuZXdkYXRhPW5uVmFsQmFsRmVhdHVyZXMpKQoKCmBgYAoKCgoKCgojIyBOZXVyYWwgTmV0d29yayBNb2RlbCBUdW5pbmcKYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCByZXN1bHRzPSdoaWRlJ30KIApjbGVhbl9ydW5zKGNvbmZpcm09RkFMU0UpCnNldC5zZWVkKDEyMykKcnVucyA8LSB0dW5pbmdfcnVuKCJhbm4uUiIsIGZsYWdzID0gbGlzdChub2RlczEgPSBjKDIwLCA1MCw3NSksIG5vZGVzMj1jKDIwLDUwLDc1KSwgZHJvcDE9YygwLjIsMC40LDAuNiksIGRyb3AyPWMoMC4yLDAuNCwwLjYpLCBsZWFybmluZ19yYXRlID0gYygwLjAxLDAuMDUsMC4wMDEsMC4wMDAxKSwgZXBvY2hzID0gYygyNSw1MCwxMDApLCBiYXRjaF9zaXplID0gYygxMCwyNSw1MCksIGFjdGl2YXRpb24gPSBjKCJyZWx1IiwgInNpZ21vaWQiLCAidGFuaCIpKSxzYW1wbGUgPSAwLjAyLCBjb25maXJtPUZBTFNFLCBlY2hvID0gRkFMU0UpCmBgYAoKYGBge3J9CnJ1bmRmIDwtIGxzX3J1bnMob3JkZXI9J21ldHJpY192YWxfYWNjdXJhY3knLCBkZWNyZWFzaW5nPVRSVUUpCnJ1bmRmIAoKYmVzdHJ1biA8LSBydW5kZlsxLDE6MTldCmBgYAoKYGBge3IgZWNobz1GQUxTRSwgcmVzdWx0cz0nYXNpcyd9CnBhbmRvYy5oZWFkZXIoIkJlc3QgUnVuIERldGFpbHMiLDMpCnBhbmRvYy50YWJsZSh0KGJlc3RydW4pKQpgYGAKCiMjIENyZWF0ZSBtb2RlbCB1c2luZyBwYXJhbXRlcnMgZnJvbSBiZXN0IHR1bmluZyBydW4KCgoKYGBge3IgaW5jbHVkZT1UUlVFLCByZXN1bHRzPSdoaWRlJ30KbW9kZWwgPC0ga2VyYXNfbW9kZWxfc2VxdWVudGlhbCgpCm1vZGVsICU+JQogIGxheWVyX2RlbnNlKHVuaXRzID0gNTAsIGFjdGl2YXRpb24gPSAicmVsdSIsIGlucHV0X3NoYXBlID0gZGltKG5uVHJhaW5GZWF0dXJlcylbMl0pICU+JQogIGxheWVyX2Ryb3BvdXQocmF0ZT0wLjQpICU+JQogIGxheWVyX2RlbnNlKHVuaXRzID0gNTAsIGFjdGl2YXRpb24gPSAicmVsdSIpICU+JQogIGxheWVyX2Ryb3BvdXQocmF0ZT0wLjYpICU+JQogIGxheWVyX2RlbnNlKHVuaXRzID0gMSwgYWN0aXZhdGlvbiA9ICdzaWdtb2lkJykKCm1vZGVsICU+JSBjb21waWxlKAogIG9wdGltaXplciA9IG9wdGltaXplcl9hZGFtKGxyPTAuMDUpLAogIAogIGxvc3M9J2JpbmFyeV9jcm9zc2VudHJvcHknLAogIG1ldHJpY3MgPSBjKCdhY2N1cmFjeScpCiAKKQoKc2V0LnNlZWQoMjM0KQpoaXN0b3J5IDwtIG1vZGVsICU+JSBmaXQoYXMubWF0cml4KG5uVHJhaW5GZWF0dXJlcyksIGFzLm1hdHJpeChublRyYWluTGFiZWxzKSwgZXBvY2hzPTUwLCBiYXRjaF9zaXplPTEwLHZhbGlkYXRpb25fZGF0YT1saXN0KGFzLm1hdHJpeChublZhbEZlYXR1cmVzKSwgYXMubWF0cml4KG5uVmFsTGFiZWxzKSkpCmBgYAojIyMgRXZhbHVhdGUgTk4gTW9kZWwgUGVyZm9ybWFuY2UKYGBge3J9CnNldC5zZWVkKDEyMykKbW9kZWwgJT4lIGV2YWx1YXRlKGFzLm1hdHJpeChublZhbEZlYXR1cmVzKSwgYXMubWF0cml4KG5uVmFsTGFiZWxzKSkKCm5uX3ByZWQgPC0gbW9kZWwgJT4lIHByZWRpY3RfY2xhc3Nlcyhhcy5tYXRyaXgobm5WYWxGZWF0dXJlcykpIApubl9wcm9iIDwtIG1vZGVsICU+JSBwcmVkaWN0X3Byb2JhKGFzLm1hdHJpeChublZhbEZlYXR1cmVzKSkKbm5fcHJvYiA8LSBkYXRhLmZyYW1lKG5uX3Byb2IpCmNvbG5hbWVzKG5uX3Byb2IpIDwtIGMoJ1llcycpCm5uX3Byb2IkTm8gPC0gMSAtIG5uX3Byb2IkWWVzCm5uX3Jlc3VsdHMyIDwtIGRhdGEuZnJhbWUobm5fcHJlZCwgbm5WYWxMYWJlbHMsIG5uX3Byb2IpIAoKY29sbmFtZXMobm5fcmVzdWx0czIpIDwtIGMoInByZWQiLCAib2JzIiwgIlllcyIsICJObyIpCm5uX3Jlc3VsdHMyJHByZWQgPC1hcy5mYWN0b3IoaWZlbHNlKG5uX3Jlc3VsdHMyJHByZWQgPT0gMCwgJ05vJywgJ1llcycpKQpubl9yZXN1bHRzMiRvYnMgPC1hcy5mYWN0b3IoaWZlbHNlKG5uX3Jlc3VsdHMyJG9icyA9PSAwLCAnTm8nLCAnWWVzJykpCm5uXzJjbGFzcyA8LSB0d29DbGFzc1N1bW1hcnkobm5fcmVzdWx0czIsIGxldj1jKCJObyIsICJZZXMiKSkKY29uZnVzaW9uTWF0cml4KG5uX3Jlc3VsdHMyJHByZWQsbm5fcmVzdWx0czIkb2JzLCBwb3NpdGl2ZT0nWWVzJykKbm5fMmNsYXNzWzFdCnBsb3QoaGlzdG9yeSkKbm5fcm9jIDwtIHJvYyhublZhbExhYmVscyRzdHJva2UgfiBubl9wcm9iJFllcywgcGxvdD1UUlVFLCBwcmludC5hdWM9VFJVRSwgY29sPSdibGFjaycsIGx3ZD00LCBsZWdhY3kuYXhlcz1UUlVFLCBtYWluPSJST0MgQ3VydmUiLCBhZGQ9RkFMU0UpCgpgYGAKCgoKIyBNb2RlbCBDb21wYXJpc29uCmBgYHtyIGVjaG89RkFMU0UsIHJlc3VsdHM9J2FzaXMnfQpjb21wYXJlZCA8LSBkYXRhLmZyYW1lKGtubl8yY2xhc3MsIGdsbV8yY2xhc3MsIHRyZWVfMmNsYXNzLCByZl8yY2xhc3MsIGxzdm1fMmNsYXNzLCBnYm1fMmNsYXNzLCBubl8yY2xhc3MpCmNvbG5hbWVzKGNvbXBhcmVkKSA8LSBjKCJrbm4iLCAiZ2xtbmV0IiwgInJwYXJ0IiwgInJmIiwgInN2bUxpbmVhciIsICJnYm0iLCAibmV1cmFsIG5ldHdvcmsiKQpjb21wYXJlZCA8LSB0KGNvbXBhcmVkKQpjb2xuYW1lcyhjb21wYXJlZCkgPC0gYygiUk9DIiwgIlNwZWMiLCAiU2VucyIpCnBhbmRvYy5oZWFkZXIoIk1vZGVsIFBlcmZvcm1hY2UgQ29tcGFyaXNvbiIsMykKcGFuZG9jLnRhYmxlKGNvbXBhcmVkKQpgYGAgCgpgYGB7ciByZXN1bHRzPSdoaWRlJ30gCnByZWRfcmY9IHByZWRpY3Rpb24ocmZfcHJvYiRZZXMsdmFsRGF0YUxhYmVscyRzdHJva2UpCgpwZXJmb3JtYW5jZShwcmVkX3JmLCBtZWFzdXJlID0gImF1YyIpQHkudmFsdWVzCnJmX3JvYyA8LSByb2ModmFsRGF0YUxhYmVscyRzdHJva2UgfiByZl9wcm9iJFllcywgcGxvdD1UUlVFLCBwcmludC5hdWM9RkFMU0UsIGNvbD0nZ3JlZW4nLCBsd2Q9NCwgbGVnYWN5LmF4ZXM9VFJVRSwgbWFpbj0iUk9DIEN1cnZlIikKCnByZWRfa25uID0gcHJlZGljdGlvbihrbm5fcHJvYiRZZXMsIHZhbERhdGFMYWJlbHMkc3Ryb2tlKQpwZXJmb3JtYW5jZShwcmVkX2tubiwgbWVhc3VyZT0nYXVjJylAeS52YWx1ZXMKa25uX3JvYyA8LSByb2ModmFsRGF0YUxhYmVscyRzdHJva2UgfiBrbm5fcHJvYiRZZXMsIHBsb3Q9VFJVRSwgcHJpbnQuYXVjPUZBTFNFLCBjb2w9J2JsdWUnLCBsd2Q9NCwgbGVnYWN5LmF4ZXM9VFJVRSwgbWFpbj0iUk9DIEN1cnZlIiwgYWRkPVRSVUUpCgpwcmVkX2dsbSA8LSBwcmVkaWN0aW9uKGdsbV9wcm9iJFllcywgdmFsRGF0YUxhYmVscyRzdHJva2UpCmdsbV9wZXJmb3JtIDwtIHBlcmZvcm1hbmNlKHByZWRfZ2xtLCBtZWFzdXJlPSdhdWMnKUB5LnZhbHVlcwpnbG1fcm9jIDwtIHJvYyh2YWxEYXRhTGFiZWxzJHN0cm9rZSB+IGdsbV9wcm9iJFllcywgcGxvdD1UUlVFLCBwcmludC5hdWM9RkFMU0UsIGNvbD0nb3JhbmdlJywgbHdkPTQsIGxlZ2FjeS5heGVzPVRSVUUsIG1haW49IlJPQyBDdXJ2ZSIsIGFkZD1UUlVFKQoKcHJlZF90cmVlIDwtIHByZWRpY3Rpb24odHJlZV9wcm9iJFllcywgdmFsRGF0YUxhYmVscyRzdHJva2UpCnBlcmZvcm1hbmNlKHByZWRfdHJlZSwgbWVhc3VyZT0nYXVjJylAeS52YWx1ZXMKdHJlZV9yb2MgPC0gcm9jKHZhbERhdGFMYWJlbHMkc3Ryb2tlIH4gdHJlZV9wcm9iJFllcywgcGxvdD1UUlVFLCBwcmludC5hdWM9RkFMU0UsIGNvbD0neWVsbG93JywgbHdkPTQsIGxlZ2FjeS5heGVzPVRSVUUsIG1haW49IlJPQyBDdXJ2ZSIsIGFkZD1UUlVFKQoKCnByZWRfbHN2bSA8LSBwcmVkaWN0aW9uKGxzdm1fcHJvYiRZZXMsIHZhbERhdGFMYWJlbHMkc3Ryb2tlKQpwZXJmb3JtYW5jZShwcmVkX2xzdm0sIG1lYXN1cmU9J2F1YycpQHkudmFsdWVzCmxzdm1fcm9jIDwtIHJvYyh2YWxEYXRhTGFiZWxzJHN0cm9rZSB+IGxzdm1fcHJvYiRZZXMsIHBsb3Q9VFJVRSwgcHJpbnQuYXVjPUZBTFNFLCBjb2w9J3B1cnBsZScsIGx3ZD00LCBsZWdhY3kuYXhlcz1UUlVFLCBtYWluPSJST0MgQ3VydmUiLCBhZGQ9VFJVRSkKCnByZWRfZ2JtIDwtIHByZWRpY3Rpb24oZ2JtX3Byb2IkWWVzLCB2YWxEYXRhTGFiZWxzJHN0cm9rZSkKcGVyZm9ybWFuY2UocHJlZF9nYm0sIG1lYXN1cmU9J2F1YycpQHkudmFsdWVzCmdibV9yb2MgPC0gcm9jKHZhbERhdGFMYWJlbHMkc3Ryb2tlIH4gZ2JtX3Byb2IkWWVzLCBwbG90PVRSVUUsIHByaW50LmF1Yz1GQUxTRSwgY29sPSdibGFjaycsIGx3ZD00LCBsZWdhY3kuYXhlcz1UUlVFLCBtYWluPSJST0MgQ3VydmUiLCBhZGQ9VFJVRSkKCnByZWRfbm4gPSBwcmVkaWN0aW9uKG5uX3Byb2IkWWVzLG5uVmFsTGFiZWxzJHN0cm9rZSkKcGVyZm9ybWFuY2UocHJlZF9ubiwgbWVhc3VyZT0nYXVjJylAeS52YWx1ZXMKbm5fcm9jIDwtIHJvYyhublZhbExhYmVscyRzdHJva2UgfiBubl9wcm9iJFllcywgcGxvdD1UUlVFLCBwcmludC5hdWM9RkFMU0UsIGNvbD0nYnJvd24nLCBsd2Q9NCwgbGVnYWN5LmF4ZXM9VFJVRSwgbWFpbj0iUk9DIEN1cnZlIiwgYWRkPVRSVUUpCgpsZWdlbmQoImJvdHRvbXJpZ2h0IiwgbGVnZW5kPWMoImtubiIsICdyZicsICdnbG1uZXQnLCAncnBhcnQnLCAnc3ZtTGluZWFyJywgJ2dibScsICduZXVyYWwgbmV0d29yaycpLCBjb2w9YygiYmx1ZSIsICJncmVlbiIsICdvcmFuZ2UnLCAneWVsbG93JywgJ3B1cnBsZScsICdibGFjaycsICdicm93bicpLCBsd2Q9MikKCmBgYAoKCgoKIyBCdWlsZCBGaW5hbCBNb2RlbCAgIAojIyMgQmFzZWQgdXBvbiBUcmFpbi9WYWxpZGF0aW9uIHJ1bnMgb2YgdGhlIG1vZGVscywgdGhlIEVsYXN0aWMgTmV0IG1vZGVsIGhhdmluZyBST0M6IDAuODQ5MywgU3BlY2lmaWNpdHk6IDAuNzA3OSBhbmQgU2Vuc2l0aXZpdHk6IDAuODUwMCBzaG93ZWQgdGhlIGJlc3QgcGVyZm9ybWFuY2UuIFdlIHdpbGwgbm93IHRyYWluIGEgZ2xtbmV0IG1vZGVsIHVzaW5nIHRoZSBmdWxsIHRyYWluaW5nIGRhdGEgc2V0IGFuZCBldmFsdWF0ZSBpdCB1c2luZyB0aGUgdGVzdCBkYXRhc2V0LgpgYGB7cn0KZnVsbERhdGEgPC0gY2JpbmQodHJhaW5EYXRhRnVsbEJhbGFuY2VkRmVhdHVyZXNfc2UsIHRyYWluRGF0YUZ1bGxCYWxhbmNlZExhYmVscykKCmZpbmFsX21vZGVsIDwtIGdsbW5ldCh4PWFzLm1hdHJpeCh0cmFpbkRhdGFGdWxsQmFsYW5jZWRGZWF0dXJlc19zZSksIHk9YXMubWF0cml4KHRyYWluRGF0YUZ1bGxCYWxhbmNlZExhYmVscyksIGZhbWlseT0nYmlub21pYWwnLCBhbHBoYT1nbG1fbW9kZWwkYmVzdFR1bmUkYWxwaGEsIGxhbWJkYT1nbG1fbW9kZWwkYmVzdFR1bmUkbGFtYmRhKQpmaW5hbF9wcmVkaWN0aW9uIDwtIHByZWRpY3QoZmluYWxfbW9kZWwsIGFzLm1hdHJpeCh0ZXN0RGF0YUZlYXR1cmVzX3NlKSx0eXBlPSdjbGFzcycpCmZpbmFsX3Byb2JzIDwtIHByZWRpY3QoZmluYWxfbW9kZWwsIGFzLm1hdHJpeCh0ZXN0RGF0YUZlYXR1cmVzX3NlKSx0eXBlPSdyZXNwb25zZScpCmZpbmFsX3ByZWRpY3Rpb24gPC0gYXMuZmFjdG9yKGZpbmFsX3ByZWRpY3Rpb24pCmZpbmFsX3Jlc3VsdHMgPC0gZGF0YS5mcmFtZShmaW5hbF9wcmVkaWN0aW9uLCB0ZXN0RGF0YUxhYmVscywgZmluYWxfcHJvYnMpCmNvbG5hbWVzKGZpbmFsX3Jlc3VsdHMpIDwtIGMoInByZWQiLCAib2JzIiwgIlllcyIpCmZpbmFsX3Jlc3VsdHMkTm8gPC0gMS0gZmluYWxfcmVzdWx0cyRZZXMKCnByZWRfZmluYWw9IHByZWRpY3Rpb24oZmluYWxfcmVzdWx0cyRZZXMsdGVzdERhdGFMYWJlbHMkc3Ryb2tlKQoKcGVyZm9ybWFuY2UocHJlZF9maW5hbCwgbWVhc3VyZSA9ICJhdWMiKUB5LnZhbHVlcwpmaW5hbF9yb2MgPC0gcm9jKHRlc3REYXRhTGFiZWxzJHN0cm9rZSB+IGZpbmFsX3Jlc3VsdHMkWWVzLCBwbG90PVRSVUUsIAogICAgICAgICAgICAgICAgIHByaW50LmF1Yz1UUlVFLCBjb2w9J3JlZCcsIGx3ZD00LCBsZWdhY3kuYXhlcz1UUlVFLCBtYWluPSJST0MgQ3VydmUiKQpjb25mdXNpb25NYXRyaXgoZmluYWxfcHJlZGljdGlvbiwgdGVzdERhdGFMYWJlbHMkc3Ryb2tlLCBwb3NpdGl2ZT0iWWVzIikKY29lZihmaW5hbF9tb2RlbCkKCmBgYAoKIyMjIEZpbmFsIG1ldHJpY3MgZm9yIHRoZSBnbG1uZXQgbW9kZWwgYXJlIGNvbnNpc3RlbnQgd2l0aCB3aGF0IHdhcyBmb3VuZCBkdXJpbmcgdGhlIGNyb3NzLXZhbGlkYXRpb24gYW5kIHR1bmluZyBwcm9jZXNzCg==